[gitg] Improved GitgRunner/GitgCommand/GitgShell



commit f9ef65b59dbe9724dc9578567b37e7ecae3f5235
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Mon Oct 25 09:50:05 2010 +0200

    Improved GitgRunner/GitgCommand/GitgShell

 Makefile.am                        |    2 +-
 configure.ac                       |    2 +
 gitg/gitg-branch-actions.c         |  548 ++++++++++++-------
 gitg/gitg-branch-actions.h         |   18 +-
 gitg/gitg-commit-view.c            |  119 +++--
 gitg/gitg-dnd.c                    |   15 +-
 gitg/gitg-repository-dialog.c      |   65 ++-
 gitg/gitg-revision-changes-panel.c |  184 ++++---
 gitg/gitg-revision-details-panel.c |   62 +-
 gitg/gitg-revision-files-panel.c   |   60 +-
 gitg/gitg-window.c                 |   33 +-
 gitg/gitg-window.h                 |   17 +-
 libgitg/Makefile.am                |   12 +-
 libgitg/gitg-command.c             |  491 +++++++++++++++++
 libgitg/gitg-command.h             |   76 +++
 libgitg/gitg-commit.c              | 1050 +++++++++++++++++++++++-------------
 libgitg/gitg-commit.h              |   60 ++-
 libgitg/gitg-config.c              |  179 +++----
 libgitg/gitg-config.h              |   35 +-
 libgitg/gitg-io.c                  |  406 ++++++++++++++
 libgitg/gitg-io.h                  |   70 +++
 libgitg/gitg-line-parser.c         |  452 ++++++++++++++++
 libgitg/gitg-line-parser.h         |   45 ++
 libgitg/gitg-repository.c          |  433 +++------------
 libgitg/gitg-repository.h          |   28 +-
 libgitg/gitg-runner.c              | 1016 ++++++++++-------------------------
 libgitg/gitg-runner.h              |   93 +---
 libgitg/gitg-shell.c               | 1052 ++++++++++++++++++++++++++++++++++++
 libgitg/gitg-shell.h               |  153 ++++++
 tests/Makefile.am                  |   12 +
 tests/shell.c                      |  273 ++++++++++
 tools/Makefile.am                  |   10 +
 tools/gitg-shell.c                 |  206 +++++++
 33 files changed, 5142 insertions(+), 2135 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 4e97269..a3a9d14 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 ## Process this file with automake to produce Makefile.in
 
-SUBDIRS = libgitg gitg data po
+SUBDIRS = libgitg gitg data po tests tools
 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = libgitg-1.0.pc
diff --git a/configure.ac b/configure.ac
index 5aa6cd5..abdba9a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -144,6 +144,8 @@ data/Makefile
 data/gitg.desktop.in
 data/icons/Makefile
 po/Makefile.in
+tests/Makefile
+tools/Makefile
 ])
 
 AC_OUTPUT
diff --git a/gitg/gitg-branch-actions.c b/gitg/gitg-branch-actions.c
index 17ef96e..12da232 100644
--- a/gitg/gitg-branch-actions.c
+++ b/gitg/gitg-branch-actions.c
@@ -38,7 +38,7 @@ typedef void (*ProgressCallback) (GitgWindow *window, GitgProgress progress, gpo
 typedef struct
 {
 	GitgWindow *window;
-	GitgRunner *runner;
+	GitgShell *shell;
 
 	ProgressCallback callback;
 	gpointer callback_data;
@@ -59,7 +59,7 @@ free_progress_info (ProgressInfo *info)
 
 	gtk_widget_destroy (GTK_WIDGET (info->dialog));
 
-	g_object_unref (info->runner);
+	g_object_unref (info->shell);
 	g_slice_free (ProgressInfo, info);
 }
 
@@ -81,7 +81,7 @@ parse_valist (va_list ap)
 }
 
 static void
-on_progress_end (GitgRunner *runner, gboolean cancelled, ProgressInfo *info)
+on_progress_end (GitgShell *shell, gboolean cancelled, ProgressInfo *info)
 {
 	GitgProgress progress;
 
@@ -89,7 +89,7 @@ on_progress_end (GitgRunner *runner, gboolean cancelled, ProgressInfo *info)
 	{
 		progress = GITG_PROGRESS_CANCELLED;
 	}
-	else if (gitg_runner_get_exit_status (runner) != 0)
+	else if (gitg_io_get_exit_status (GITG_IO (shell)) != 0)
 	{
 		progress = GITG_PROGRESS_ERROR;
 	}
@@ -109,7 +109,7 @@ on_progress_end (GitgRunner *runner, gboolean cancelled, ProgressInfo *info)
 static void
 on_progress_response (GtkDialog *dialog, GtkResponseType response, ProgressInfo *info)
 {
-	gitg_runner_cancel (info->runner);
+	gitg_io_cancel (GITG_IO (info->shell));
 }
 
 static gboolean
@@ -119,7 +119,7 @@ on_progress_timeout (ProgressInfo *info)
 	return TRUE;
 }
 
-static GitgRunner *
+static GitgShell *
 run_progress (GitgWindow       *window,
               gchar const      *title,
               gchar const      *message,
@@ -129,19 +129,18 @@ run_progress (GitgWindow       *window,
 {
 	va_list ap;
 
-	// Create runner
 	va_start (ap, callback_data);
 
-	GitgRunner *runner = gitg_runner_new (1000);
+	GitgShell *shell = gitg_shell_new (1000);
 	gchar const **argv = parse_valist (ap);
 
-	if (!gitg_repository_run_command (gitg_window_get_repository (window),
-	                                  runner,
-	                                  argv,
-	                                  NULL))
+	GitgCommand *cmd = gitg_command_new (gitg_window_get_repository (window),
+	                                     (gchar const * const *)argv);
+
+	if (!gitg_shell_run (shell, cmd, NULL))
 	{
 		g_free (argv);
-		g_object_unref (runner);
+		g_object_unref (shell);
 
 		callback (window, GITG_PROGRESS_ERROR, callback_data);
 
@@ -183,14 +182,14 @@ run_progress (GitgWindow       *window,
 	info->callback = callback;
 	info->callback_data = callback_data;
 	info->window = window;
-	info->runner = g_object_ref (runner);
+	info->shell = g_object_ref (shell);
 
 	info->timeout_id = g_timeout_add (100, (GSourceFunc)on_progress_timeout, info);
 
 	g_signal_connect (dlg, "response", G_CALLBACK (on_progress_response), info);
-	g_signal_connect (runner, "end-loading", G_CALLBACK (on_progress_end), info);
+	g_signal_connect (shell, "end", G_CALLBACK (on_progress_end), info);
 
-	return runner;
+	return shell;
 }
 
 static gint
@@ -257,14 +256,19 @@ message_dialog (GitgWindow     *window,
 	return ret;
 }
 
-static GitgRunner *
+static GitgShell *
 remove_local_branch (GitgWindow *window,
                      GitgRef    *ref)
 {
 	gchar const *name = gitg_ref_get_shortname (ref);
 	GitgRepository *repository = gitg_window_get_repository (window);
 
-	if (!gitg_repository_commandv (repository, NULL, "branch", "-d", name, NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "branch",
+	                                             "-d",
+	                                             name,
+	                                             NULL),
+	                          NULL))
 	{
 		gint ret = message_dialog (window,
 		                           GTK_MESSAGE_ERROR,
@@ -275,7 +279,12 @@ remove_local_branch (GitgWindow *window,
 
 		if (ret == GTK_RESPONSE_ACCEPT)
 		{
-			if (!gitg_repository_commandv (repository, NULL, "branch", "-D", name, NULL))
+			if (!gitg_shell_run_sync (gitg_command_newv (repository,
+			                                             "branch",
+			                                             "-D",
+			                                             name,
+			                                             NULL),
+			                          NULL))
 			{
 				message_dialog (window,
 				                GTK_MESSAGE_ERROR,
@@ -325,7 +334,7 @@ on_remove_remote_result (GitgWindow *window, GitgProgress progress, gpointer dat
 	gitg_ref_free (ref);
 }
 
-static GitgRunner *
+static GitgShell *
 remove_remote_branch (GitgWindow *window,
                       GitgRef    *ref)
 {
@@ -346,7 +355,7 @@ remove_remote_branch (GitgWindow *window,
 	gchar const *local = gitg_ref_get_local_name (ref);
 	gchar *rm = g_strconcat (":", local, NULL);
 
-	GitgRunner *ret;
+	GitgShell *ret;
 	gchar *message = g_strdup_printf ("Removing remote branch `%s'", name);
 
 	ret = run_progress (window,
@@ -368,14 +377,15 @@ get_stash_refspec (GitgRepository *repository, GitgRef *stash)
 {
 	gchar **out;
 
-	out = gitg_repository_command_with_outputv (repository,
-	                                            NULL,
-	                                            "log",
-	                                            "--no-color",
-	                                            "--pretty=oneline",
-	                                            "-g",
-	                                            "refs/stash",
-	                                            NULL);
+	out = gitg_shell_run_sync_with_output (gitg_command_newv (repository,
+	                                                          "log",
+	                                                          "--no-color",
+	                                                          "--pretty=oneline",
+	                                                          "-g",
+	                                                          "refs/stash",
+	                                                          NULL),
+	                                       FALSE,
+	                                       NULL);
 
 	gchar **ptr = out;
 	gchar *sha1 = gitg_hash_hash_to_sha1_new (gitg_ref_get_hash (stash));
@@ -403,7 +413,7 @@ get_stash_refspec (GitgRepository *repository, GitgRef *stash)
 	return ret;
 }
 
-static GitgRunner *
+static GitgShell *
 remove_stash (GitgWindow *window, GitgRef *ref)
 {
 	gint r = message_dialog (window,
@@ -425,14 +435,14 @@ remove_stash (GitgWindow *window, GitgRef *ref)
 		return NULL;
 	}
 
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "reflog",
-	                               "delete",
-	                               "--updateref",
-	                               "--rewrite",
-	                               spec,
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "reflog",
+	                                             "delete",
+	                                             "--updateref",
+	                                             "--rewrite",
+	                                             spec,
+	                                             NULL),
+	                          NULL))
 	{
 		message_dialog (window,
 		                GTK_MESSAGE_ERROR,
@@ -442,19 +452,19 @@ remove_stash (GitgWindow *window, GitgRef *ref)
 	}
 	else
 	{
-		if (!gitg_repository_commandv (repository,
-		                               NULL,
-		                               "rev-parse",
-		                               "--verify",
-		                               "refs/stash {0}",
-		                               NULL))
+		if (!gitg_shell_run_sync (gitg_command_newv (repository,
+		                                             "rev-parse",
+		                                             "--verify",
+		                                             "refs/stash {0}",
+		                                             NULL),
+		                          NULL))
 		{
-			gitg_repository_commandv (repository,
-			                          NULL,
-			                          "update-ref",
-			                          "-d",
-			                          "refs/stash",
-			                          NULL);
+			gitg_shell_run_sync (gitg_command_newv (repository,
+			                                        "update-ref",
+			                                        "-d",
+			                                        "refs/stash",
+			                                        NULL),
+			                     NULL);
 		}
 
 		gitg_repository_reload (repository);
@@ -464,7 +474,7 @@ remove_stash (GitgWindow *window, GitgRef *ref)
 	return NULL;
 }
 
-static GitgRunner *
+static GitgShell *
 remove_tag (GitgWindow *window, GitgRef *ref)
 {
 	gchar const *name = gitg_ref_get_shortname (ref);
@@ -484,12 +494,12 @@ remove_tag (GitgWindow *window, GitgRef *ref)
 
 	GitgRepository *repository = gitg_window_get_repository (window);
 
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "tag",
-	                               "-d",
-	                               name,
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "tag",
+	                                             "-d",
+	                                             name,
+	                                             NULL),
+	                          NULL))
 	{
 		message = g_strdup_printf (_ ("The tag <%s> could not be successfully removed"),
 		                           name);
@@ -508,7 +518,7 @@ remove_tag (GitgWindow *window, GitgRef *ref)
 	}
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_remove (GitgWindow *window,
                             GitgRef    *ref)
 {
@@ -516,7 +526,7 @@ gitg_branch_actions_remove (GitgWindow *window,
 	g_return_val_if_fail (ref != NULL, NULL);
 
 	GitgRef *cp = gitg_ref_copy (ref);
-	GitgRunner *ret = NULL;
+	GitgShell *ret = NULL;
 
 	switch (gitg_ref_get_ref_type (cp))
 	{
@@ -540,7 +550,7 @@ gitg_branch_actions_remove (GitgWindow *window,
 	return ret;
 }
 
-static GitgRunner *
+static GitgShell *
 rename_branch (GitgWindow  *window,
                GitgRef     *ref,
                const gchar *newname)
@@ -548,7 +558,13 @@ rename_branch (GitgWindow  *window,
 	gchar const *oldname = gitg_ref_get_shortname (ref);
 	GitgRepository *repository = gitg_window_get_repository (window);
 
-	if (!gitg_repository_commandv (repository, NULL, "branch", "-m", oldname, newname, NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "branch",
+	                                             "-m",
+	                                             oldname,
+	                                             newname,
+	                                             NULL),
+	                          NULL))
 	{
 		gint ret = message_dialog (window,
 		                           GTK_MESSAGE_ERROR,
@@ -559,7 +575,13 @@ rename_branch (GitgWindow  *window,
 
 		if (ret == GTK_RESPONSE_ACCEPT)
 		{
-			if (!gitg_repository_commandv (repository, NULL, "branch", "-M", oldname, newname, NULL))
+			if (!gitg_shell_run_sync (gitg_command_newv (repository,
+			                                             "branch",
+			                                             "-M",
+			                                             oldname,
+			                                             newname,
+			                                             NULL),
+			                         NULL))
 			{
 				message_dialog (window,
 				                GTK_MESSAGE_ERROR,
@@ -635,7 +657,7 @@ rename_dialog (GitgWindow *window, const gchar *oldname)
 	return newname;
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_rename (GitgWindow *window,
                             GitgRef    *ref)
 {
@@ -649,7 +671,7 @@ gitg_branch_actions_rename (GitgWindow *window,
 		if (newname)
 		{
 			GitgRef *cp = gitg_ref_copy (ref);
-			GitgRunner *ret = NULL;
+			GitgShell *ret = NULL;
 			ret = rename_branch (window, cp, newname);
 			gitg_ref_free (cp);
 			g_free (newname);
@@ -661,13 +683,13 @@ gitg_branch_actions_rename (GitgWindow *window,
 }
 
 static void
-reset_buffer (GitgRunner *runner, GString *buffer)
+reset_buffer (GitgShell *shell, GString *buffer)
 {
 	g_string_erase (buffer, 0, -1);
 }
 
 static void
-update_buffer (GitgRunner *runner, gchar **lines, GString *buffer)
+update_buffer (GitgShell *shell, gchar **lines, GString *buffer)
 {
 	gchar **ptr = lines;
 
@@ -686,12 +708,26 @@ update_buffer (GitgRunner *runner, gchar **lines, GString *buffer)
 static gboolean
 no_changes (GitgRepository *repository)
 {
-	return gitg_repository_commandv (repository, NULL,
-	                                 "update-index", "--refresh", NULL) &&
-	       gitg_repository_commandv (repository, NULL,
-	                                 "diff-files", "--quiet", NULL) &&
-	       gitg_repository_commandv (repository, NULL,
-	                                 "diff-index", "--cached", "--quiet", "HEAD", "--", NULL);
+	return gitg_shell_run_sync (gitg_command_newv (repository,
+	                                               "update-index",
+	                                               "--refresh",
+	                                               NULL),
+	                            NULL) &&
+	       gitg_shell_run_sync (gitg_command_newv (repository,
+	                                               "diff-files",
+	                                               "--no-ext-diff",
+	                                               "--quiet",
+	                                               NULL),
+	                            NULL) &&
+	       gitg_shell_run_sync (gitg_command_newv (repository,
+	                                               "diff-index",
+	                                               "--no-ext-diff",
+	                                               "--cached",
+	                                               "--quiet",
+	                                               "HEAD",
+	                                               "--",
+	                                               NULL),
+	                            NULL);
 }
 
 static gboolean
@@ -705,11 +741,11 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 	gchar *msg = NULL;
 	gboolean showerror = FALSE;
 
-	GitgRunner *runner = gitg_runner_new_synchronized (1000);
+	GitgShell *shell = gitg_shell_new_synchronized (1000);
 	GString *buffer = g_string_new ("");
 
-	g_signal_connect (runner, "begin-loading", G_CALLBACK (reset_buffer), buffer);
-	g_signal_connect (runner, "update", G_CALLBACK (update_buffer), buffer);
+	g_signal_connect (shell, "begin", G_CALLBACK (reset_buffer), buffer);
+	g_signal_connect (shell, "update", G_CALLBACK (update_buffer), buffer);
 
 	gchar const *secondary;
 
@@ -734,9 +770,17 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 		goto cleanup;
 	}
 
-	gitg_repository_run_commandv (repository, runner, NULL,
-	                              "log", "--no-color", "--abbrev-commit",
-	                              "--pretty=oneline", "-n", "1", "HEAD", NULL);
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "log",
+	                                   "--no-color",
+	                                   "--abbrev-commit",
+	                                   "--pretty=oneline",
+	                                   "-n",
+	                                   "1",
+	                                   "HEAD",
+	                                   NULL),
+	                NULL);
 
 	GitgRef *working = gitg_repository_get_current_working_ref (repository);
 
@@ -750,8 +794,11 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 	}
 
 	// Create tree object of the current index
-	gitg_repository_run_commandv (repository, runner, NULL,
-	                              "write-tree", NULL);
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "write-tree",
+	                                   NULL),
+	                NULL);
 
 	if (buffer->len == 0)
 	{
@@ -765,10 +812,22 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 	head = gitg_repository_parse_head (repository);
 
 	gchar *idxmsg = g_strconcat ("index on ", msg, NULL);
-	gitg_repository_run_command_with_inputv (repository, runner, idxmsg, NULL,
-	                              "commit-tree", tree, "-p", head, NULL);
+
+	GInputStream *inp = g_memory_input_stream_new_from_data (idxmsg, -1, NULL);
+	gitg_io_set_input (GITG_IO (shell), inp);
+	g_object_unref (inp);
+
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "commit-tree",
+	                                   tree,
+	                                   "-p",
+	                                   head,
+	                                   NULL),
+	                NULL);
 
 	g_free (idxmsg);
+	gitg_io_set_input (GITG_IO (shell), NULL);
 
 	if (buffer->len == 0)
 	{
@@ -814,23 +873,48 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 	}
 
 	tmpname = g_file_get_path (customindex);
-	gitg_runner_add_environment (runner, "GIT_INDEX_FILE", tmpname);
+
+	GitgCommand *cmd_read_tree = gitg_command_newv (repository,
+	                                                "read-tree",
+	                                                "-m",
+	                                                tree,
+	                                                NULL);
+
+	gitg_command_add_environmentv (cmd_read_tree,
+	                               "GIT_INDEX_FILE",
+	                               tmpname,
+	                               NULL);
+
+	GitgCommand *cmd_add = gitg_command_newv (repository,
+	                                          "add",
+	                                          "-u",
+	                                          NULL);
+
+	gitg_command_add_environmentv (cmd_add,
+	                               "GIT_INDEX_FILE",
+	                               tmpname,
+	                               NULL);
+
+	GitgCommand *cmd_write_tree = gitg_command_newv (repository,
+	                                                 "write-tree",
+	                                                 NULL);
+
+	gitg_command_add_environmentv (cmd_write_tree,
+	                               "GIT_INDEX_FILE",
+	                               tmpname,
+	                               NULL);
+
 	g_free (tmpname);
 
 	gboolean writestash;
 
-	writestash = gitg_repository_run_commandv (repository, runner, NULL,
-	                                           "read-tree", "-m", tree, NULL) &&
-	             gitg_repository_run_commandv (repository, runner, NULL,
-	                                           "add", "-u", NULL) &&
-	             gitg_repository_run_commandv (repository, runner, NULL,
-	                                           "write-tree", NULL);
+	writestash = gitg_shell_run (shell, cmd_read_tree, NULL) &&
+	             gitg_shell_run (shell, cmd_add, NULL) &&
+	             gitg_shell_run (shell, cmd_write_tree, NULL);
 
 	g_file_delete (customindex, NULL, NULL);
 	g_object_unref (customindex);
 
-	gitg_runner_set_environment (runner, NULL);
-
 	if (!writestash)
 	{
 		ret = FALSE;
@@ -842,10 +926,23 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 	gchar *stashtree = g_strndup (buffer->str, buffer->len);
 	gchar *reason = g_strconcat ("gitg auto stash: ", msg, NULL);
 
-	gitg_repository_run_command_with_inputv (repository, runner, reason, NULL,
-	                                         "commit-tree", stashtree,
-	                                         "-p", head,
-	                                         "-p", commit, NULL);
+	inp = g_memory_input_stream_new_from_data (reason, -1, NULL);
+	gitg_io_set_input (GITG_IO (shell), inp);
+	g_object_unref (inp);
+
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "commit-tree",
+	                                   stashtree,
+	                                   "-p",
+	                                   head,
+	                                   "-p",
+	                                   commit,
+	                                   NULL),
+	                NULL);
+
+	gitg_io_set_input (GITG_IO (shell), NULL);
+
 	g_free (stashtree);
 
 	if (buffer->len == 0)
@@ -887,19 +984,30 @@ stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 
 	g_free (path);
 
-	gitg_repository_run_commandv (repository, runner, NULL,
-	                              "update-ref", "-m", reason,
-	                              "refs/stash", rref, NULL);
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "update-ref",
+	                                   "-m",
+	                                   reason,
+	                                   "refs/stash",
+	                                   rref,
+	                                   NULL),
+	                NULL);
 
 	g_free (rref);
 
-	gitg_repository_run_commandv (repository, runner, NULL,
-	                              "reset", "--hard", NULL);
+	gitg_shell_run (shell,
+	                gitg_command_newv (repository,
+	                                   "reset",
+	                                   "--hard",
+	                                   NULL),
+	                NULL);
+
 	ret = TRUE;
 
 cleanup:
 	g_string_free (buffer, TRUE);
-	g_object_unref (runner);
+	g_object_unref (shell);
 	g_free (commit);
 	g_free (tree);
 	g_free (head);
@@ -938,14 +1046,11 @@ checkout_local_branch_real (GitgWindow *window, GitgRef *ref)
 {
 	GitgRepository *repository = gitg_window_get_repository (window);
 
-	if (!gitg_repository_commandv (repository, NULL, "checkout", gitg_ref_get_shortname (ref), NULL))
-	{
-		return FALSE;
-	}
-	else
-	{
-		return TRUE;
-	}
+	return gitg_shell_run_sync (gitg_command_newv (repository,
+	                                               "checkout",
+	                                               gitg_ref_get_shortname (ref),
+	                                               NULL),
+	                            NULL);
 }
 
 static gboolean
@@ -990,14 +1095,14 @@ checkout_remote_branch (GitgWindow *window,
 	gchar const *local = gitg_ref_get_local_name (ref);
 	gboolean ret;
 
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "checkout",
-	                               "--track",
-	                               "-b",
-	                               local,
-	                               name,
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "checkout",
+	                                             "--track",
+	                                             "-b",
+	                                             local,
+	                                             name,
+	                                             NULL),
+	                          NULL))
 	{
 		message_dialog (window,
 		                GTK_MESSAGE_ERROR,
@@ -1030,13 +1135,13 @@ checkout_tag (GitgWindow *window,
 	gchar const *name = gitg_ref_get_shortname (ref);
 	gboolean ret;
 
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "checkout",
-	                               "-b",
-	                               name,
-	                               name,
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "checkout",
+	                                             "-b",
+	                                             name,
+	                                             name,
+	                                             NULL),
+	                         NULL))
 	{
 		message_dialog (window,
 		                GTK_MESSAGE_ERROR,
@@ -1139,14 +1244,14 @@ on_merge_rebase_result (GitgWindow   *window,
 		}
 
 		message_dialog (window,
-			            GTK_MESSAGE_ERROR,
-			            message,
-			            NULL,
-			            NULL,
-	                    gitg_ref_get_ref_type (info->source) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
-	                    gitg_ref_get_shortname (info->source),
-	                    gitg_ref_get_ref_type (info->dest) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
-	                    gitg_ref_get_shortname (info->dest));
+		                GTK_MESSAGE_ERROR,
+		                message,
+		                NULL,
+		                NULL,
+		                gitg_ref_get_ref_type (info->source) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
+		                gitg_ref_get_shortname (info->source),
+		                gitg_ref_get_ref_type (info->dest) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
+		                gitg_ref_get_shortname (info->dest));
 	}
 	else if (progress == GITG_PROGRESS_SUCCESS)
 	{
@@ -1159,32 +1264,43 @@ on_merge_rebase_result (GitgWindow   *window,
 
 			if (info->stashcommit)
 			{
-				gitg_repository_commandv (repository, NULL,
-				                          "update-ref", "-m", "gitg autosave stash",
-				                          "refs/stash", info->stashcommit, NULL);
+				gitg_shell_run_sync (gitg_command_newv (repository,
+				                                        "update-ref",
+				                                        "-m",
+				                                        "gitg autosave stash",
+				                                        "refs/stash",
+				                                        info->stashcommit,
+				                                        NULL),
+				                     NULL);
+
 				message = _ ("The stashed changes have been stored to be reapplied manually");
 			}
 
 			message_dialog (window,
-				            GTK_MESSAGE_ERROR,
-				            _ ("Failed to checkout previously checked out branch"),
-				            message,
-				            NULL);
+			                GTK_MESSAGE_ERROR,
+			                _ ("Failed to checkout previously checked out branch"),
+			                message,
+			                NULL);
 		}
 		else if (info->stashcommit)
 		{
 			// Reapply stash
-			if (!gitg_repository_commandv (gitg_window_get_repository (window),
-			                               NULL,
-			                               "stash",
-			                               "apply",
-			                               "--index",
-			                               info->stashcommit,
-			                               NULL))
+			if (!gitg_shell_run_sync (gitg_command_newv (gitg_window_get_repository (window),
+			                                             "stash",
+			                                             "apply",
+			                                             "--index",
+			                                             info->stashcommit,
+			                                             NULL),
+			                          NULL))
 			{
-				gitg_repository_commandv (repository, NULL,
-				                          "update-ref", "-m", "gitg autosave stash",
-				                          "refs/stash", info->stashcommit, NULL);
+				gitg_shell_run_sync (gitg_command_newv (repository,
+				                                        "update-ref",
+				                                        "-m",
+				                                        "gitg autosave stash",
+				                                        "refs/stash",
+				                                        info->stashcommit,
+				                                        NULL),
+				                     NULL);
 
 				message_dialog (window,
 				                GTK_MESSAGE_ERROR,
@@ -1200,7 +1316,7 @@ on_merge_rebase_result (GitgWindow   *window,
 	ref_info_free (info);
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_merge (GitgWindow *window,
                            GitgRef    *source,
                            GitgRef    *dest)
@@ -1238,7 +1354,11 @@ gitg_branch_actions_merge (GitgWindow *window,
 	GitgRef *head = gitg_repository_get_current_working_ref (repository);
 
 	// First checkout the correct branch on which to merge, e.g. dest
-	if (!gitg_repository_commandv (repository, NULL, "checkout", gitg_ref_get_shortname (dest), NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "checkout",
+	                                             gitg_ref_get_shortname (dest),
+	                                             NULL),
+	                          NULL))
 	{
 		g_free (stashcommit);
 
@@ -1257,7 +1377,7 @@ gitg_branch_actions_merge (GitgWindow *window,
 	                           gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
 	                           gitg_ref_get_shortname (dest));
 
-	GitgRunner *ret;
+	GitgShell *ret;
 	RefInfo *info = ref_info_new (source, dest);
 	info->stashcommit = stashcommit;
 	info->head = gitg_ref_copy (head);
@@ -1277,7 +1397,7 @@ gitg_branch_actions_merge (GitgWindow *window,
 	return ret;
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_rebase (GitgWindow *window,
                             GitgRef    *source,
                             GitgRef    *dest)
@@ -1340,7 +1460,7 @@ gitg_branch_actions_rebase (GitgWindow *window,
 	                           gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _ ("local") : _ ("remote"),
 	                           gitg_ref_get_shortname (dest));
 
-	GitgRunner *ret;
+	GitgShell *ret;
 	RefInfo *info = ref_info_new (source, dest);
 	info->stashcommit = stashcommit;
 	info->head = gitg_ref_copy (gitg_repository_get_current_working_ref (repository));
@@ -1387,7 +1507,7 @@ on_push_result (GitgWindow   *window,
 	ref_info_free (info);
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_push (GitgWindow *window,
                           GitgRef    *source,
                           GitgRef    *dest)
@@ -1423,7 +1543,7 @@ gitg_branch_actions_push (GitgWindow *window,
 	                           gitg_ref_get_shortname (source),
 	                           gitg_ref_get_shortname (dest));
 
-	GitgRunner *ret;
+	GitgShell *ret;
 	RefInfo *info = ref_info_new (source, dest);
 
 	ret = run_progress (window,
@@ -1442,7 +1562,7 @@ gitg_branch_actions_push (GitgWindow *window,
 	return ret;
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_push_remote (GitgWindow  *window,
                                  GitgRef     *source,
                                  gchar const *remote,
@@ -1475,7 +1595,7 @@ gitg_branch_actions_push_remote (GitgWindow  *window,
 	                           gitg_ref_get_shortname (source),
 	                           remote, branch);
 
-	GitgRunner *ret;
+	GitgShell *ret;
 	gchar *rr = g_strconcat ("refs/remotes/", remote, "/", branch, NULL);
 	GitgRef *rmref = gitg_ref_new ("0000000000000000000000000000000000000000", rr);
 	g_free (rr);
@@ -1546,13 +1666,13 @@ gitg_branch_actions_apply_stash (GitgWindow *window,
 	gchar *sha1 = gitg_hash_hash_to_sha1_new (gitg_ref_get_hash (stash));
 	gboolean ret;
 
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "stash",
-	                               "apply",
-	                               "--index",
-	                               sha1,
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "stash",
+	                                             "apply",
+	                                             "--index",
+	                                             sha1,
+	                                             NULL),
+	                          NULL))
 	{
 		message = g_strdup_printf (_ ("The stash could not be applied to local branch <%s>"),
 		                           gitg_ref_get_shortname (branch));
@@ -1590,12 +1710,12 @@ gitg_branch_actions_create (GitgWindow *window, gchar const *sha1, gchar const *
 
 	repository = gitg_window_get_repository (window);
 
-	result = gitg_repository_commandv (repository,
-	                                   NULL,
-	                                   "branch",
-	                                   name,
-	                                   sha1,
-	                                   NULL);
+	result = gitg_shell_run_sync (gitg_command_newv (repository,
+	                                                 "branch",
+	                                                 name,
+	                                                 sha1,
+	                                                 NULL),
+	                              NULL);
 
 	if (!result)
 	{
@@ -1631,24 +1751,24 @@ gitg_branch_actions_tag (GitgWindow *window, gchar const *sha1, gchar const *nam
 
 	if (message != NULL && message[0] != '\0')
 	{
-		result = gitg_repository_commandv (repository,
-		                                   NULL,
-		                                   "tag",
-		                                   "-m",
-		                                   message,
-		                                   sign ? "-s" : "-a",
-		                                   name,
-		                                   sha1,
-		                                   NULL);
+		result = gitg_shell_run_sync (gitg_command_newv (repository,
+		                                                 "tag",
+		                                                 "-m",
+		                                                 message,
+		                                                 sign ? "-s" : "-a",
+		                                                 name,
+		                                                 sha1,
+		                                                 NULL),
+		                              NULL);
 	}
 	else
 	{
-		result = gitg_repository_commandv (repository,
-		                                   NULL,
-		                                   "tag",
-		                                   name,
-		                                   sha1,
-		                                   NULL);
+		result = gitg_shell_run_sync (gitg_command_newv (repository,
+		                                                 "tag",
+		                                                 name,
+		                                                 sha1,
+		                                                 NULL),
+		                              NULL);
 	}
 
 	if (!result)
@@ -1737,9 +1857,14 @@ on_cherry_pick_result (GitgWindow   *window,
 
 			if (info->stashcommit)
 			{
-				gitg_repository_commandv (repository, NULL,
-				                          "update-ref", "-m", "gitg autosave stash",
-				                          "refs/stash", info->stashcommit, NULL);
+				gitg_shell_run_sync (gitg_command_newv (repository,
+				                                        "update-ref",
+				                                        "-m",
+				                                        "gitg autosave stash",
+				                                        "refs/stash",
+				                                        info->stashcommit,
+				                                        NULL),
+				                     NULL);
 
 				message = _ ("The stashed changes have been stored to be reapplied manually");
 			}
@@ -1753,17 +1878,22 @@ on_cherry_pick_result (GitgWindow   *window,
 		else if (info->stashcommit)
 		{
 			// Reapply stash
-			if (!gitg_repository_commandv (gitg_window_get_repository (window),
-			                               NULL,
-			                               "stash",
-			                               "apply",
-			                               "--index",
-			                               info->stashcommit,
-			                               NULL))
+			if (!gitg_shell_run_sync (gitg_command_newv (gitg_window_get_repository (window),
+			                                             "stash",
+			                                             "apply",
+			                                             "--index",
+			                                             info->stashcommit,
+			                                             NULL),
+			                          NULL))
 			{
-				gitg_repository_commandv (repository, NULL,
-				                          "update-ref", "-m", "gitg autosave stash",
-				                          "refs/stash", info->stashcommit, NULL);
+				gitg_shell_run_sync (gitg_command_newv (repository,
+				                                        "update-ref",
+				                                        "-m",
+				                                        "gitg autosave stash",
+				                                        "refs/stash",
+				                                        info->stashcommit,
+				                                        NULL),
+				                     NULL);
 
 				message_dialog (window,
 				                GTK_MESSAGE_ERROR,
@@ -1779,7 +1909,7 @@ on_cherry_pick_result (GitgWindow   *window,
 	cherry_pick_info_free (info);
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_cherry_pick (GitgWindow   *window,
                                  GitgRevision *revision,
                                  GitgRef      *dest)
@@ -1812,11 +1942,11 @@ gitg_branch_actions_cherry_pick (GitgWindow   *window,
 	GitgRef *head = gitg_repository_get_current_working_ref (repository);
 
 	// First checkout the correct branch on which to cherry-pick
-	if (!gitg_repository_commandv (repository,
-	                               NULL,
-	                               "checkout",
-	                               gitg_ref_get_shortname (dest),
-	                               NULL))
+	if (!gitg_shell_run_sync (gitg_command_newv (repository,
+	                                             "checkout",
+	                                             gitg_ref_get_shortname (dest),
+	                                             NULL),
+	                          NULL))
 	{
 		g_free (stashcommit);
 
@@ -1833,7 +1963,7 @@ gitg_branch_actions_cherry_pick (GitgWindow   *window,
 	message = g_strdup_printf (_ ("Cherry-picking on <%s>"),
 	                           gitg_ref_get_shortname (dest));
 
-	GitgRunner *ret;
+	GitgShell *ret;
 
 	CherryPickInfo *info = cherry_pick_info_new (revision, dest);
 
@@ -1908,7 +2038,7 @@ on_format_patch_result (GitgWindow   *window,
 }
 
 static void
-on_format_patch_update (GitgRunner       *runner,
+on_format_patch_update (GitgShell        *shell,
                         gchar           **lines,
                         FormatPatchInfo  *info)
 {
@@ -1920,7 +2050,7 @@ on_format_patch_update (GitgRunner       *runner,
 	}
 }
 
-GitgRunner *
+GitgShell *
 gitg_branch_actions_format_patch (GitgWindow   *window,
                                   GitgRevision *revision,
                                   gchar const  *destination)
@@ -1929,7 +2059,7 @@ gitg_branch_actions_format_patch (GitgWindow   *window,
 	g_return_val_if_fail (revision != NULL, NULL);
 	g_return_val_if_fail (destination != NULL, NULL);
 
-	GitgRunner *ret;
+	GitgShell *ret;
 
 	GFile *file = g_file_new_for_uri (destination);
 	GFileOutputStream *stream = g_file_replace (file,
diff --git a/gitg/gitg-branch-actions.h b/gitg/gitg-branch-actions.h
index 58b7f18..88fac6a 100644
--- a/gitg/gitg-branch-actions.h
+++ b/gitg/gitg-branch-actions.h
@@ -24,28 +24,28 @@
 #define __GITG_BRANCH_ACTIONS_H__
 
 #include <libgitg/gitg-ref.h>
-#include "gitg-window.h"
+#include <gitg/gitg-window.h>
 
 G_BEGIN_DECLS
 
 gboolean gitg_branch_actions_create (GitgWindow *window, gchar const *sha1, gchar const *name);
-GitgRunner *gitg_branch_actions_remove (GitgWindow *window, GitgRef *ref);
-GitgRunner *gitg_branch_actions_rename (GitgWindow *window, GitgRef *ref);
+GitgShell *gitg_branch_actions_remove (GitgWindow *window, GitgRef *ref);
+GitgShell *gitg_branch_actions_rename (GitgWindow *window, GitgRef *ref);
 gboolean gitg_branch_actions_checkout (GitgWindow *window, GitgRef *ref);
 
-GitgRunner *gitg_branch_actions_merge (GitgWindow *window, GitgRef *source, GitgRef *dest);
-GitgRunner *gitg_branch_actions_rebase (GitgWindow *window, GitgRef *source, GitgRef *dest);
+GitgShell *gitg_branch_actions_merge (GitgWindow *window, GitgRef *source, GitgRef *dest);
+GitgShell *gitg_branch_actions_rebase (GitgWindow *window, GitgRef *source, GitgRef *dest);
 
-GitgRunner *gitg_branch_actions_push (GitgWindow *window, GitgRef *source, GitgRef *dest);
-GitgRunner *gitg_branch_actions_push_remote (GitgWindow *window, GitgRef *source, gchar const *remote, gchar const *branch);
+GitgShell *gitg_branch_actions_push (GitgWindow *window, GitgRef *source, GitgRef *dest);
+GitgShell *gitg_branch_actions_push_remote (GitgWindow *window, GitgRef *source, gchar const *remote, gchar const *branch);
 
 gboolean gitg_branch_actions_apply_stash (GitgWindow *window, GitgRef *stash, GitgRef *branch);
 
 gboolean gitg_branch_actions_tag (GitgWindow *window, gchar const *sha1, gchar const *name, gchar const *message, gboolean sign);
 
-GitgRunner *gitg_branch_actions_cherry_pick (GitgWindow *window, GitgRevision *revision, GitgRef *dest);
+GitgShell *gitg_branch_actions_cherry_pick (GitgWindow *window, GitgRevision *revision, GitgRef *dest);
 
-GitgRunner *gitg_branch_actions_format_patch (GitgWindow *window, GitgRevision *revision, gchar const *destination);
+GitgShell *gitg_branch_actions_format_patch (GitgWindow *window, GitgRevision *revision, gchar const *destination);
 
 G_END_DECLS
 
diff --git a/gitg/gitg-commit-view.c b/gitg/gitg-commit-view.c
index 38bc044..8b844a9 100644
--- a/gitg/gitg-commit-view.c
+++ b/gitg/gitg-commit-view.c
@@ -26,6 +26,7 @@
 #include <glib/gi18n.h>
 #include <string.h>
 #include <libgitg/gitg-commit.h>
+#include <libgitg/gitg-shell.h>
 
 #include "gitg-commit-view.h"
 #include "gitg-diff-view.h"
@@ -78,7 +79,7 @@ struct _GitgCommitViewPrivate
 	GtkHScale *hscale_context;
 	gint context_size;
 
-	GitgRunner *runner;
+	GitgShell *shell;
 	guint update_id;
 	gboolean is_diff;
 
@@ -138,11 +139,11 @@ gitg_commit_view_finalize (GObject *object)
 
 	if (view->priv->update_id)
 	{
-		g_signal_handler_disconnect (view->priv->runner, view->priv->update_id);
+		g_signal_handler_disconnect (view->priv->shell, view->priv->update_id);
 	}
 
-	gitg_runner_cancel (view->priv->runner);
-	g_object_unref (view->priv->runner);
+	gitg_io_cancel (GITG_IO (view->priv->shell));
+	g_object_unref (view->priv->shell);
 	g_object_unref (view->priv->ui_manager);
 
 	gdk_cursor_unref (view->priv->hand);
@@ -224,7 +225,7 @@ show_binary_information (GitgCommitView *view)
 }
 
 static void
-on_changes_update (GitgRunner *runner, gchar **buffer, GitgCommitView *view)
+on_changes_update (GitgShell *shell, gchar **buffer, GitgCommitView *view)
 {
 	gchar *line;
 	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(view->priv->changes_view));
@@ -261,7 +262,7 @@ on_changes_update (GitgRunner *runner, gchar **buffer, GitgCommitView *view)
 
 		if (content_type && !gitg_utils_can_display_content_type (content_type))
 		{
-			gitg_runner_cancel (runner);
+			gitg_io_cancel (GITG_IO (shell));
 			show_binary_information (view);
 		}
 		else if (content_type)
@@ -283,7 +284,7 @@ on_changes_update (GitgRunner *runner, gchar **buffer, GitgCommitView *view)
 static void
 connect_update (GitgCommitView *view)
 {
-	view->priv->update_id = g_signal_connect (view->priv->runner,
+	view->priv->update_id = g_signal_connect (view->priv->shell,
 	                                          "update",
 	                                          G_CALLBACK (on_changes_update),
 	                                          view);
@@ -384,9 +385,11 @@ check_selection(GtkTreeView    *tree_view,
                 GitgCommitView *view)
 {
 	if (view->priv->update_id)
-		g_signal_handler_disconnect(view->priv->runner, view->priv->update_id);
+	{
+		g_signal_handler_disconnect(view->priv->shell, view->priv->update_id);
+	}
 
-	gitg_runner_cancel(view->priv->runner);
+	gitg_io_cancel(GITG_IO (view->priv->shell));
 	view->priv->update_id = 0;
 
 	GtkTextView *tv = GTK_TEXT_VIEW(view->priv->changes_view);
@@ -478,7 +481,7 @@ unstaged_selection_changed (GtkTreeSelection *selection,
 				view->priv->is_diff = FALSE;
 				connect_update (view);
 
-				gitg_runner_run_stream (view->priv->runner, stream, NULL);
+				gitg_shell_run_stream (view->priv->shell, stream, NULL);
 				g_object_unref (stream);
 			}
 		}
@@ -487,6 +490,8 @@ unstaged_selection_changed (GtkTreeSelection *selection,
 	}
 	else
 	{
+		gboolean allow_external;
+
 		set_diff_language (view);
 		view->priv->is_diff = TRUE;
 		connect_update (view);
@@ -496,15 +501,22 @@ unstaged_selection_changed (GtkTreeSelection *selection,
 		gchar ct[10];
 		g_snprintf (ct, sizeof(ct), "-U%d", view->priv->context_size);
 
-		gitg_repository_run_commandv (view->priv->repository,
-		                              view->priv->runner,
-		                              NULL,
-		                              "diff",
-		                              "--no-color",
-		                              ct,
-		                              "--",
-		                              path,
-		                              NULL);
+		g_object_get (gitg_preferences_get_default (),
+		              "diff-external",
+		              &allow_external,
+		              NULL);
+
+		gitg_shell_run (view->priv->shell,
+		                gitg_command_newv (view->priv->repository,
+		                                   "diff",
+		                                   allow_external ? "--ext-diff" : "--no-ext-diff",
+		                                   "--no-color",
+		                                   ct,
+		                                   "--",
+		                                   path,
+		                                   NULL),
+		                NULL);
+
 		g_free (path);
 	}
 
@@ -521,7 +533,9 @@ staged_selection_changed (GtkTreeSelection *selection, GitgCommitView *view)
 	GtkTreeIter iter;
 
 	if (!check_selection (view->priv->tree_view_staged, &iter, view))
+	{
 		return;
+	}
 
 	model = gtk_tree_view_get_model (view->priv->tree_view_staged);
 	unselect_tree_view (view->priv->tree_view_unstaged);
@@ -561,14 +575,15 @@ staged_selection_changed (GtkTreeSelection *selection, GitgCommitView *view)
 			connect_update (view);
 
 			gchar *indexpath = g_strconcat (":0:", path, NULL);
-			gitg_repository_run_commandv (view->priv->repository,
-			                              view->priv->runner,
-			                              NULL,
-			                              "show",
-			                              "--encoding=UTF-8",
-			                              "--no-color",
-			                              indexpath,
-			                              NULL);
+			gitg_shell_run (view->priv->shell,
+			                gitg_command_newv (view->priv->repository,
+			                                   "show",
+			                                   "--encoding=UTF-8",
+			                                   "--no-color",
+			                                   indexpath,
+			                                   NULL),
+			                NULL);
+
 			g_free (indexpath);
 		}
 
@@ -576,6 +591,8 @@ staged_selection_changed (GtkTreeSelection *selection, GitgCommitView *view)
 	}
 	else
 	{
+		gboolean allow_external;
+
 		view->priv->is_diff = TRUE;
 		set_diff_language (view);
 		connect_update (view);
@@ -584,17 +601,24 @@ staged_selection_changed (GtkTreeSelection *selection, GitgCommitView *view)
 		gchar ct[10];
 		g_snprintf (ct, sizeof(ct), "-U%d", view->priv->context_size);
 
-		gitg_repository_run_commandv (view->priv->repository,
-		                              view->priv->runner,
-		                              NULL,
-		                              "diff-index",
-		                              ct,
-		                              "--cached",
-		                              "--no-color",
-		                              head,
-		                              "--",
-		                              path,
-		                              NULL);
+		g_object_get (gitg_preferences_get_default (),
+		              "diff-external",
+		              &allow_external,
+		              NULL);
+
+		gitg_shell_run (view->priv->shell,
+		                gitg_command_newv (view->priv->repository,
+		                                   "diff-index",
+		                                   allow_external ? "--ext-diff" : "--no-ext-diff",
+		                                   ct,
+		                                   "--cached",
+		                                   "--no-color",
+		                                   head,
+		                                   "--",
+		                                   path,
+		                                   NULL),
+		                NULL);
+
 		g_free(head);
 	}
 
@@ -1659,8 +1683,8 @@ gitg_commit_view_init (GitgCommitView *self)
 {
 	self->priv = GITG_COMMIT_VIEW_GET_PRIVATE (self);
 
-	self->priv->runner = gitg_runner_new (10000);
-	gitg_runner_set_preserve_line_endings (self->priv->runner, TRUE);
+	self->priv->shell = gitg_shell_new (10000);
+	gitg_shell_set_preserve_line_endings (self->priv->shell, TRUE);
 
 	self->priv->hand = gdk_cursor_new (GDK_HAND1);
 }
@@ -2174,7 +2198,10 @@ do_revert_changes(GitgCommitView *view)
 
 		for (item = files; item; item = g_list_next (item))
 		{
-			ret &= gitg_commit_revert(view->priv->commit, GITG_CHANGED_FILE (item->data), NULL, NULL);
+ 			ret &= gitg_commit_undo (view->priv->commit,
+ 			                         GITG_CHANGED_FILE (item->data),
+ 			                         NULL,
+ 			                         NULL);
 			g_object_unref (item->data);
 		}
 
@@ -2185,11 +2212,17 @@ do_revert_changes(GitgCommitView *view)
 		GitgChangedFile *file = g_object_ref(view->priv->current_file);
 
 		gchar *hunk = get_hunk_patch(view, &view->priv->context_iter);
-		ret = gitg_commit_revert(view->priv->commit, view->priv->current_file, hunk, NULL);
+		ret = gitg_commit_undo (view->priv->commit,
+		                        view->priv->current_file,
+		                        hunk,
+		                        NULL);
 		g_free(hunk);
 
 		if (ret && view->priv->current_file == file)
-			gitg_diff_view_remove_hunk(GITG_DIFF_VIEW(view->priv->changes_view), &view->priv->context_iter);
+		{
+			gitg_diff_view_remove_hunk (GITG_DIFF_VIEW(view->priv->changes_view),
+			                            &view->priv->context_iter);
+		}
 
 		g_object_unref(file);
 	}
diff --git a/gitg/gitg-dnd.c b/gitg/gitg-dnd.c
index b5e41be..e393c1a 100644
--- a/gitg/gitg-dnd.c
+++ b/gitg/gitg-dnd.c
@@ -777,13 +777,14 @@ revision_to_text (GitgRepository *repository,
 	gchar **lines;
 	gchar *sha1 = gitg_revision_get_sha1 (revision);
 
-	lines = gitg_repository_command_with_outputv (repository,
-	                                              NULL,
-	                                              "log",
-	                                              "-1",
-	                                              "--pretty=format:%h: %s%n%n%b",
-	                                              sha1,
-	                                              NULL);
+	lines = gitg_shell_run_sync_with_output (gitg_command_newv (repository,
+	                                                            "log",
+	                                                            "-1",
+	                                                            "--pretty=format:%h: %s%n%n%b",
+	                                                            sha1,
+	                                                            NULL),
+	                                         FALSE,
+	                                         NULL);
 
 	remove_trailing_newlines (lines);
 	gchar *ret = g_strjoinv ("\n", lines);
diff --git a/gitg/gitg-repository-dialog.c b/gitg/gitg-repository-dialog.c
index c1dc6ac..aceebd9 100644
--- a/gitg/gitg-repository-dialog.c
+++ b/gitg/gitg-repository-dialog.c
@@ -24,6 +24,7 @@
 
 #include <stdlib.h>
 #include <libgitg/gitg-config.h>
+#include <libgitg/gitg-shell.h>
 
 #include "gitg-repository-dialog.h"
 #include "gitg-utils.h"
@@ -100,7 +101,7 @@ G_DEFINE_TYPE (GitgRepositoryDialog, gitg_repository_dialog, GTK_TYPE_DIALOG)
 typedef struct
 {
 	GitgRepositoryDialog *dialog;
-	GitgRunner *runner;
+	GitgShell *shell;
 	GtkTreeRowReference *reference;
 
 #ifdef BUILD_SPINNER
@@ -149,7 +150,7 @@ fetch_cleanup (FetchInfo *info)
 #endif
 
 	gtk_tree_row_reference_free (info->reference);
-	g_object_unref (info->runner);
+	g_object_unref (info->shell);
 
 	g_slice_free (FetchInfo, info);
 }
@@ -174,7 +175,7 @@ gitg_repository_dialog_finalize (GObject *object)
 
 	for (item = copy; item; item = g_list_next (item))
 	{
-		gitg_runner_cancel (((FetchInfo *)item->data)->runner);
+		gitg_io_cancel (GITG_IO (((FetchInfo *)item->data)->shell));
 	}
 
 	g_list_free (copy);
@@ -341,7 +342,7 @@ pulse_row (FetchInfo *info)
 #endif
 
 static void
-on_fetch_begin_loading (GitgRunner *runner, FetchInfo *info)
+on_fetch_begin_loading (GitgShell *shell, FetchInfo *info)
 {
 	GtkTreeIter iter;
 	GtkTreePath *path = gtk_tree_row_reference_get_path (info->reference);
@@ -387,7 +388,7 @@ on_fetch_begin_loading (GitgRunner *runner, FetchInfo *info)
 }
 
 static void
-on_fetch_end_loading (GitgRunner *runner, gboolean cancelled, FetchInfo *info)
+on_fetch_end_loading (GitgShell *shell, gboolean cancelled, FetchInfo *info)
 {
 	if (cancelled || !gtk_tree_row_reference_valid (info->reference))
 	{
@@ -406,7 +407,7 @@ on_fetch_end_loading (GitgRunner *runner, gboolean cancelled, FetchInfo *info)
 static void
 fetch_remote (GitgRepositoryDialog *dialog, GtkTreeIter *iter)
 {
-	GitgRunner *runner = gitg_runner_new (1000);
+	GitgShell *shell = gitg_shell_new (1000);
 	FetchInfo *info = g_slice_new0 (FetchInfo);
 	GtkTreeModel *model = GTK_TREE_MODEL (dialog->priv->list_store_remotes);
 
@@ -414,17 +415,17 @@ fetch_remote (GitgRepositoryDialog *dialog, GtkTreeIter *iter)
 
 	info->dialog = dialog;
 	info->reference = gtk_tree_row_reference_new (model, path);
-	info->runner = runner;
+	info->shell = shell;
 
 	gtk_tree_path_free (path);
 
-	g_signal_connect (runner,
-	                  "begin-loading",
+	g_signal_connect (shell,
+	                  "begin",
 	                  G_CALLBACK (on_fetch_begin_loading),
 	                  info);
 
-	g_signal_connect (runner,
-	                  "end-loading",
+	g_signal_connect (shell,
+	                  "end",
 	                  G_CALLBACK (on_fetch_end_loading),
 	                  info);
 
@@ -433,12 +434,12 @@ fetch_remote (GitgRepositoryDialog *dialog, GtkTreeIter *iter)
 	gchar *name;
 	gtk_tree_model_get (model, iter, COLUMN_NAME, &name, -1);
 
-	gitg_repository_run_commandv (dialog->priv->repository,
-	                              runner,
-	                              NULL,
-	                              "fetch",
-	                              name,
-	                              NULL);
+	gitg_shell_run (shell,
+	                gitg_command_newv (dialog->priv->repository,
+	                                   "fetch",
+	                                   name,
+	                                   NULL),
+	                NULL);
 
 	g_free (name);
 }
@@ -684,7 +685,7 @@ fetch_remote_cancel (GitgRepositoryDialog *dialog,
 
 		if (equal)
 		{
-			gitg_runner_cancel (info->runner);
+			gitg_io_cancel (GITG_IO (info->shell));
 			break;
 		}
 	}
@@ -736,12 +737,12 @@ on_button_fetch_remote_clicked (GtkButton            *button,
 static gboolean
 remove_remote (GitgRepositoryDialog *dialog, gchar const *name)
 {
-	return gitg_repository_commandv (dialog->priv->repository,
-	                                 NULL,
-	                                 "remote",
-	                                 "rm",
-	                                 name,
-	                                 NULL);
+	return gitg_shell_run_sync (gitg_command_newv (dialog->priv->repository,
+	                                               "remote",
+	                                               "rm",
+	                                               name,
+	                                               NULL),
+	                            NULL);
 }
 
 void
@@ -828,7 +829,13 @@ on_button_add_remote_clicked (GtkButton *button,
 	gchar *name = g_strdup_printf ("remote%d", num + 1);
 	gchar const url[] = "git://example.com/repository.git";
 
-	if (gitg_repository_commandv (dialog->priv->repository, NULL, "remote", "add", name, url, NULL))
+	if (gitg_shell_run_sync (gitg_command_newv (dialog->priv->repository,
+	                                            "remote",
+	                                            "add",
+	                                            name,
+	                                            url,
+	                                            NULL),
+	                         NULL))
 	{
 		GtkTreeIter iter;
 		GtkTreePath *path;
@@ -901,7 +908,13 @@ on_remote_name_edited (GtkCellRendererText *renderer,
 	                    COLUMN_URL, &url,
 	                    -1);
 
-	if (gitg_repository_commandv (dialog->priv->repository, NULL, "remote", "add", new_text, url, NULL))
+	if (gitg_shell_run_sync (gitg_command_newv (dialog->priv->repository,
+	                                            "remote",
+	                                            "add",
+	                                            new_text,
+	                                            url,
+	                                            NULL),
+	                         NULL))
 	{
 		remove_remote (dialog, oldname);
 
diff --git a/gitg/gitg-revision-changes-panel.c b/gitg/gitg-revision-changes-panel.c
index bf58635..0aef963 100644
--- a/gitg/gitg-revision-changes-panel.c
+++ b/gitg/gitg-revision-changes-panel.c
@@ -6,12 +6,14 @@
 #include <string.h>
 #include <libgitg/gitg-repository.h>
 #include <libgitg/gitg-revision.h>
-#include <libgitg/gitg-runner.h>
+#include <libgitg/gitg-shell.h>
 #include <libgitg/gitg-hash.h>
 #include "gitg-diff-view.h"
 #include "gitg-utils.h"
+#include "gitg-preferences.h"
 #include <glib/gi18n.h>
 
+
 #include "gitg-revision-panel.h"
 #include "gitg-activatable.h"
 
@@ -26,8 +28,8 @@ struct _GitgRevisionChangesPanelPrivate
 	GtkTreeView *diff_files;
 	GtkListStore *list_store_diff_files;
 
-	GitgRunner *diff_runner;
-	GitgRunner *diff_files_runner;
+	GitgShell *diff_shell;
+	GitgShell *diff_files_shell;
 
 	GitgRepository *repository;
 	GitgRevision *revision;
@@ -520,16 +522,16 @@ gitg_revision_changes_panel_dispose (GObject *object)
 
 	set_revision (changes_panel, NULL, NULL);
 
-	if (changes_panel->priv->diff_files_runner)
+	if (changes_panel->priv->diff_files_shell)
 	{
-		g_object_unref (changes_panel->priv->diff_files_runner);
-		changes_panel->priv->diff_files_runner = NULL;
+		g_object_unref (changes_panel->priv->diff_files_shell);
+		changes_panel->priv->diff_files_shell = NULL;
 	}
 
-	if (changes_panel->priv->diff_files_runner)
+	if (changes_panel->priv->diff_files_shell)
 	{
-		g_object_unref (changes_panel->priv->diff_runner);
-		changes_panel->priv->diff_runner = NULL;
+		g_object_unref (changes_panel->priv->diff_shell);
+		changes_panel->priv->diff_shell = NULL;
 	}
 
 	if (changes_panel->priv->builder)
@@ -564,8 +566,8 @@ reload_diff (GitgRevisionChangesPanel *changes_panel)
 	GtkTreeSelection *selection;
 
 	// First cancel a possibly still running diff
-	gitg_runner_cancel (changes_panel->priv->diff_runner);
-	gitg_runner_cancel (changes_panel->priv->diff_files_runner);
+	gitg_io_cancel (GITG_IO (changes_panel->priv->diff_shell));
+	gitg_io_cancel (GITG_IO (changes_panel->priv->diff_files_shell));
 
 	free_cached_headers (changes_panel);
 
@@ -592,45 +594,54 @@ reload_diff (GitgRevisionChangesPanel *changes_panel)
 	}
 
 	gchar sign = gitg_revision_get_sign (changes_panel->priv->revision);
+	gboolean allow_external;
+
+	g_object_get (gitg_preferences_get_default (),
+	              "diff-external",
+	              &allow_external,
+	              NULL);
 
 	switch (sign)
 	{
 		case 't':
-			gitg_repository_run_commandv (changes_panel->priv->repository,
-			                              changes_panel->priv->diff_runner,
-			                              NULL,
-			                              "diff",
-			                              "--cached",
-			                              "-M",
-			                              "--pretty=format:",
-			                              "--encoding=UTF-8",
-			                              "--no-color",
-			                              NULL);
+			gitg_shell_run (changes_panel->priv->diff_shell,
+			                gitg_command_newv (changes_panel->priv->repository,
+			                                   "diff",
+			                                   allow_external ? "--ext-diff" : "--no-ext-diff",
+			                                   "--cached",
+			                                   "-M",
+			                                   "--pretty=format:",
+			                                   "--encoding=UTF-8",
+			                                   "--no-color",
+			                                   NULL),
+			                NULL);
 		break;
 		case 'u':
-			gitg_repository_run_commandv (changes_panel->priv->repository,
-			                              changes_panel->priv->diff_runner,
-			                              NULL,
-			                              "diff",
-			                              "-M",
-			                              "--pretty=format:",
-			                              "--encoding=UTF-8",
-			                              "--no-color",
-			                              NULL);
+			gitg_shell_run (changes_panel->priv->diff_shell,
+			                gitg_command_newv (changes_panel->priv->repository,
+			                                   "diff",
+			                                   allow_external ? "--ext-diff" : "--no-ext-diff",
+			                                   "-M",
+			                                   "--pretty=format:",
+			                                   "--encoding=UTF-8",
+			                                   "--no-color",
+			                                   NULL),
+			                NULL);
 		break;
 		default:
 		{
 			gchar *hash = gitg_revision_get_sha1 (changes_panel->priv->revision);
-			gitg_repository_run_commandv (changes_panel->priv->repository,
-			                              changes_panel->priv->diff_runner,
-			                              NULL,
-			                              "show",
-			                              "-M",
-			                              "--pretty=format:",
-			                              "--encoding=UTF-8",
-			                              "--no-color",
-			                              hash,
-			                              NULL);
+
+			gitg_shell_run (changes_panel->priv->diff_shell,
+			                gitg_command_newv (changes_panel->priv->repository,
+			                                   "show",
+			                                   "-M",
+			                                   "--pretty=format:",
+			                                   "--encoding=UTF-8",
+			                                   "--no-color",
+			                                   hash,
+			                                   NULL),
+			                NULL);
 
 			g_free (hash);
 		}
@@ -649,14 +660,14 @@ set_revision (GitgRevisionChangesPanel *changes_panel,
 		return;
 	}
 
-	if (changes_panel->priv->diff_runner)
+	if (changes_panel->priv->diff_shell)
 	{
-		gitg_runner_cancel (changes_panel->priv->diff_runner);
+		gitg_io_cancel (GITG_IO (changes_panel->priv->diff_shell));
 	}
 
-	if (changes_panel->priv->diff_files_runner)
+	if (changes_panel->priv->diff_files_shell)
 	{
-		gitg_runner_cancel (changes_panel->priv->diff_files_runner);
+		gitg_io_cancel (GITG_IO (changes_panel->priv->diff_files_shell));
 	}
 
 	if (changes_panel->priv->repository)
@@ -691,7 +702,7 @@ set_revision (GitgRevisionChangesPanel *changes_panel,
 }
 
 static void
-on_diff_files_begin_loading (GitgRunner               *runner,
+on_diff_files_begin_loading (GitgShell                *shell,
                              GitgRevisionChangesPanel *self)
 {
 	GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
@@ -703,7 +714,7 @@ on_diff_files_begin_loading (GitgRunner               *runner,
 }
 
 static void
-on_diff_files_end_loading (GitgRunner               *runner,
+on_diff_files_end_loading (GitgShell                *shell,
                            gboolean                  cancelled,
                            GitgRevisionChangesPanel *self)
 {
@@ -766,7 +777,7 @@ add_diff_file (GitgRevisionChangesPanel *view,
 }
 
 static void
-on_diff_files_update (GitgRunner                *runner,
+on_diff_files_update (GitgShell                 *shell,
                       gchar                    **buffer,
                       GitgRevisionChangesPanel  *self)
 {
@@ -808,7 +819,7 @@ on_diff_files_update (GitgRunner                *runner,
 }
 
 static void
-on_diff_begin_loading (GitgRunner               *runner,
+on_diff_begin_loading (GitgShell                *shell,
                        GitgRevisionChangesPanel *self)
 {
 	GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
@@ -818,7 +829,7 @@ on_diff_begin_loading (GitgRunner               *runner,
 }
 
 static void
-on_diff_end_loading (GitgRunner               *runner,
+on_diff_end_loading (GitgShell                *shell,
                      gboolean                  cancelled,
                      GitgRevisionChangesPanel *self)
 {
@@ -831,6 +842,12 @@ on_diff_end_loading (GitgRunner               *runner,
 	}
 
 	gchar sign = gitg_revision_get_sign (self->priv->revision);
+	gboolean allow_external;
+
+	g_object_get (gitg_preferences_get_default (),
+	              "diff-external",
+	              &allow_external,
+	              NULL);
 
 	if (sign == 't' || sign == 'u')
 	{
@@ -840,38 +857,39 @@ on_diff_end_loading (GitgRunner               *runner,
 		if (sign == 't')
 			cached = "--cached";
 
-		gitg_repository_run_commandv (self->priv->repository,
-		                              self->priv->diff_files_runner,
-		                              NULL,
-		                              "diff-index",
-		                              "--raw",
-		                              "-M",
-		                              "--abbrev=40",
-		                              head,
-		                              cached,
-		                              NULL);
+		gitg_shell_run (self->priv->diff_files_shell,
+		                gitg_command_newv (self->priv->repository,
+		                                   "diff-index",
+		                                   allow_external ? "--ext-diff" : "--no-ext-diff",
+		                                   "--raw",
+		                                   "-M",
+		                                   "--abbrev=40",
+		                                   head,
+		                                   cached,
+		                                   NULL),
+		                NULL);
 		g_free (head);
 	}
 	else
 	{
 		gchar *sha = gitg_revision_get_sha1 (self->priv->revision);
-		gitg_repository_run_commandv (self->priv->repository,
-		                              self->priv->diff_files_runner,
-		                              NULL,
-		                              "show",
-		                              "--encoding=UTF-8",
-		                              "--raw",
-		                              "-M",
-		                              "--pretty=format:",
-		                              "--abbrev=40",
-		                              sha,
-		                              NULL);
+		gitg_shell_run (self->priv->diff_files_shell,
+		                gitg_command_newv (self->priv->repository,
+		                                   "show",
+		                                   "--encoding=UTF-8",
+		                                   "--raw",
+		                                   "-M",
+		                                   "--pretty=format:",
+		                                   "--abbrev=40",
+		                                   sha,
+		                                   NULL),
+		                NULL);
 		g_free (sha);
 	}
 }
 
 static void
-on_diff_update (GitgRunner                *runner,
+on_diff_update (GitgShell                 *shell,
                 gchar                    **buffer,
                 GitgRevisionChangesPanel  *self)
 {
@@ -893,37 +911,37 @@ gitg_revision_changes_panel_init (GitgRevisionChangesPanel *self)
 {
 	self->priv = GITG_REVISION_CHANGES_PANEL_GET_PRIVATE (self);
 
-	self->priv->diff_runner = gitg_runner_new (2000);
+	self->priv->diff_shell = gitg_shell_new (2000);
 
-	g_signal_connect (self->priv->diff_runner,
-	                  "begin-loading",
+	g_signal_connect (self->priv->diff_shell,
+	                  "begin",
 	                  G_CALLBACK (on_diff_begin_loading),
 	                  self);
 
-	g_signal_connect (self->priv->diff_runner,
+	g_signal_connect (self->priv->diff_shell,
 	                  "update",
 	                  G_CALLBACK (on_diff_update),
 	                  self);
 
-	g_signal_connect (self->priv->diff_runner,
-	                  "end-loading",
+	g_signal_connect (self->priv->diff_shell,
+	                  "end",
 	                  G_CALLBACK (on_diff_end_loading),
 	                  self);
 
-	self->priv->diff_files_runner = gitg_runner_new (2000);
+	self->priv->diff_files_shell = gitg_shell_new (2000);
 
-	g_signal_connect (self->priv->diff_files_runner,
-	                  "begin-loading",
+	g_signal_connect (self->priv->diff_files_shell,
+	                  "begin",
 	                  G_CALLBACK(on_diff_files_begin_loading),
 	                  self);
 
-	g_signal_connect (self->priv->diff_files_runner,
+	g_signal_connect (self->priv->diff_files_shell,
 	                  "update",
 	                  G_CALLBACK(on_diff_files_update),
 	                  self);
 
-	g_signal_connect (self->priv->diff_files_runner,
-	                  "end-loading",
+	g_signal_connect (self->priv->diff_files_shell,
+	                  "end",
 	                  G_CALLBACK(on_diff_files_end_loading),
 	                  self);
 }
diff --git a/gitg/gitg-revision-details-panel.c b/gitg/gitg-revision-details-panel.c
index 67c17d7..f879131 100644
--- a/gitg/gitg-revision-details-panel.c
+++ b/gitg/gitg-revision-details-panel.c
@@ -54,7 +54,7 @@ struct _GitgRevisionDetailsPanelPrivate
 	GitgRepository *repository;
 	GitgRevision *revision;
 
-	GitgRunner *runner;
+	GitgShell *shell;
 	gboolean in_stat;
 
 	GSList *stats;
@@ -202,12 +202,12 @@ gitg_revision_details_panel_dispose (GObject *object)
 		panel->priv->builder = NULL;
 	}
 
-	if (panel->priv->runner)
+	if (panel->priv->shell)
 	{
-		gitg_runner_cancel (panel->priv->runner);
-		g_object_unref (panel->priv->runner);
+		gitg_io_cancel (GITG_IO (panel->priv->shell));
+		g_object_unref (panel->priv->shell);
 
-		panel->priv->runner = NULL;
+		panel->priv->shell = NULL;
 	}
 
 	G_OBJECT_CLASS (gitg_revision_details_panel_parent_class)->dispose (object);
@@ -224,8 +224,8 @@ gitg_revision_details_panel_class_init (GitgRevisionDetailsPanelClass *klass)
 }
 
 static void
-on_runner_begin (GitgRunner               *runner,
-                 GitgRevisionDetailsPanel *panel)
+on_shell_begin (GitgShell                *shell,
+                GitgRevisionDetailsPanel *panel)
 {
 	GdkCursor *cursor;
 
@@ -363,9 +363,9 @@ make_stats_table (GitgRevisionDetailsPanel *panel)
 }
 
 static void
-on_runner_end (GitgRunner               *runner,
-               gboolean                  cancelled,
-               GitgRevisionDetailsPanel *panel)
+on_shell_end (GitgShell                *shell,
+              gboolean                  cancelled,
+              GitgRevisionDetailsPanel *panel)
 {
 	gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (panel->priv->text_view)),
 	                       NULL);
@@ -432,9 +432,9 @@ add_stat (GitgRevisionDetailsPanel *panel,
 }
 
 static void
-on_runner_update (GitgRunner                *runner,
-                  gchar                    **lines,
-                  GitgRevisionDetailsPanel  *panel)
+on_shell_update (GitgShell                 *shell,
+                 gchar                    **lines,
+                 GitgRevisionDetailsPanel  *panel)
 {
 	GtkTextBuffer *buffer;
 	GtkTextIter end;
@@ -476,21 +476,21 @@ gitg_revision_details_panel_init (GitgRevisionDetailsPanel *self)
 {
 	self->priv = GITG_REVISION_DETAILS_PANEL_GET_PRIVATE(self);
 
-	self->priv->runner = gitg_runner_new (1000);
+	self->priv->shell = gitg_shell_new (1000);
 
-	g_signal_connect (self->priv->runner,
-	                  "begin-loading",
-	                  G_CALLBACK (on_runner_begin),
+	g_signal_connect (self->priv->shell,
+	                  "begin",
+	                  G_CALLBACK (on_shell_begin),
 	                  self);
 
-	g_signal_connect (self->priv->runner,
-	                  "end-loading",
-	                  G_CALLBACK (on_runner_end),
+	g_signal_connect (self->priv->shell,
+	                  "end",
+	                  G_CALLBACK (on_shell_end),
 	                  self);
 
-	g_signal_connect (self->priv->runner,
+	g_signal_connect (self->priv->shell,
 	                  "update",
-	                  G_CALLBACK (on_runner_update),
+	                  G_CALLBACK (on_shell_update),
 	                  self);
 }
 
@@ -644,7 +644,7 @@ update_details (GitgRevisionDetailsPanel *panel)
 {
 	gchar *sha1;
 
-	gitg_runner_cancel (panel->priv->runner);
+	gitg_io_cancel (GITG_IO (panel->priv->shell));
 
 	gtk_text_buffer_set_text (gtk_text_view_get_buffer (panel->priv->text_view),
 	                          "",
@@ -657,14 +657,14 @@ update_details (GitgRevisionDetailsPanel *panel)
 
 	sha1 = gitg_revision_get_sha1 (panel->priv->revision);
 
-	gitg_repository_run_commandv (panel->priv->repository,
-	                              panel->priv->runner,
-	                              NULL,
-	                              "show",
-	                              "--numstat",
-	                              "--pretty=format:%s%n%n%b%n\x01",
-	                              sha1,
-	                              NULL);
+	gitg_shell_run (panel->priv->shell,
+	                gitg_command_newv (panel->priv->repository,
+	                                   "show",
+	                                   "--numstat",
+	                                   "--pretty=format:%s%n%n%b%n\x01",
+	                                   sha1,
+	                                   NULL),
+	                NULL);
 
 	g_free (sha1);
 }
diff --git a/gitg/gitg-revision-files-panel.c b/gitg/gitg-revision-files-panel.c
index 985cd1c..94f1534 100644
--- a/gitg/gitg-revision-files-panel.c
+++ b/gitg/gitg-revision-files-panel.c
@@ -27,7 +27,7 @@
 #include <gio/gio.h>
 #include <stdlib.h>
 #include <libgitg/gitg-revision.h>
-#include <libgitg/gitg-runner.h>
+#include <libgitg/gitg-shell.h>
 
 #include "gitg-revision-files-panel.h"
 #include "gitg-utils.h"
@@ -54,7 +54,7 @@ struct _GitgRevisionFilesViewPrivate
 {
 	GtkTreeView *tree_view;
 	GtkSourceView *contents;
-	GitgRunner *content_runner;
+	GitgShell *content_shell;
 	GtkTreeStore *store;
 
 	gchar *drag_dir;
@@ -62,7 +62,7 @@ struct _GitgRevisionFilesViewPrivate
 
 	GitgRepository *repository;
 	GitgRevision *revision;
-	GitgRunner *loader;
+	GitgShell *loader;
 	GtkTreePath *load_path;
 
 	gboolean skipped_blank_line;
@@ -127,7 +127,7 @@ gitg_revision_files_view_finalize (GObject *object)
 		g_strfreev (self->priv->drag_files);
 	}
 
-	gitg_runner_cancel (self->priv->loader);
+	gitg_io_cancel (GITG_IO (self->priv->loader));
 	g_object_unref (self->priv->loader);
 
 	G_OBJECT_CLASS (gitg_revision_files_view_parent_class)->finalize (object);
@@ -150,7 +150,7 @@ set_revision (GitgRevisionFilesView *files_view,
 		return;
 	}
 
-	gitg_runner_cancel (files_view->priv->loader);
+	gitg_io_cancel (GITG_IO (files_view->priv->loader));
 	gtk_tree_store_clear (files_view->priv->store);
 
 	if (files_view->priv->repository)
@@ -270,7 +270,7 @@ on_selection_changed (GtkTreeSelection     *selection,
 	GtkTreeModel *model;
 	GtkTreeIter iter;
 
-	gitg_runner_cancel (tree->priv->content_runner);
+	gitg_io_cancel (GITG_IO (tree->priv->content_shell));
 
 	gtk_text_buffer_set_text (buffer, "", -1);
 
@@ -326,13 +326,13 @@ on_selection_changed (GtkTreeSelection     *selection,
 
 		gchar *id = node_identity (tree, &iter);
 
-		gitg_repository_run_commandv (tree->priv->repository,
-		                              tree->priv->content_runner,
-		                              NULL,
-		                              "show",
-		                              "--encoding=UTF-8",
-		                              id,
-		                              NULL);
+		gitg_shell_run (tree->priv->content_shell,
+		                gitg_command_newv (tree->priv->repository,
+		                                   "show",
+		                                   "--encoding=UTF-8",
+		                                   id,
+		                                   NULL),
+		                NULL);
 
 		g_free (id);
 	}
@@ -857,8 +857,8 @@ append_node (GitgRevisionFilesView *tree,
 }
 
 static void
-on_update (GitgRunner            *runner,
-           gchar                **buffer,
+on_update (GitgShell              *shell,
+           gchar                 **buffer,
            GitgRevisionFilesView  *tree)
 {
 	gchar *line;
@@ -919,9 +919,9 @@ compare_func (GtkTreeModel *model,
 }
 
 static void
-on_contents_update (GitgRunner *runner,
-                    gchar **buffer,
-                    GitgRevisionFilesView *tree)
+on_contents_update (GitgShell              *shell,
+                    gchar                 **buffer,
+                    GitgRevisionFilesView  *tree)
 {
 	gchar *line;
 	GtkTextBuffer *buf;
@@ -943,7 +943,7 @@ on_contents_update (GitgRunner *runner,
 
 		if (content_type && !gitg_utils_can_display_content_type (content_type))
 		{
-			gitg_runner_cancel (runner);
+			gitg_io_cancel (GITG_IO (shell));
 			show_binary_information (tree);
 		}
 		else
@@ -978,14 +978,14 @@ gitg_revision_files_view_init (GitgRevisionFilesView *self)
 	                                      NAME_COLUMN,
 	                                      GTK_SORT_ASCENDING);
 
-	self->priv->loader = gitg_runner_new (1000);
+	self->priv->loader = gitg_shell_new (1000);
 	g_signal_connect (self->priv->loader,
 	                  "update",
 	                  G_CALLBACK (on_update),
 	                  self);
 
-	self->priv->content_runner = gitg_runner_new (5000);
-	g_signal_connect (self->priv->content_runner,
+	self->priv->content_shell = gitg_shell_new (5000);
+	g_signal_connect (self->priv->content_shell,
 	                  "update",
 	                  G_CALLBACK (on_contents_update),
 	                  self);
@@ -1017,7 +1017,7 @@ static void
 load_node (GitgRevisionFilesView *tree,
            GtkTreeIter          *parent)
 {
-	if (gitg_runner_running (tree->priv->loader))
+	if (gitg_io_get_running (GITG_IO (tree->priv->loader)))
 	{
 		return;
 	}
@@ -1041,12 +1041,12 @@ load_node (GitgRevisionFilesView *tree,
 	}
 
 	tree->priv->skipped_blank_line = FALSE;
-	gitg_repository_run_commandv (tree->priv->repository,
-	                              tree->priv->loader,
-	                              NULL,
-	                              "show",
-	                              "--encoding=UTF-8",
-	                              id,
-	                              NULL);
+	gitg_shell_run (tree->priv->loader,
+	                gitg_command_newv (tree->priv->repository,
+	                                   "show",
+	                                   "--encoding=UTF-8",
+	                                   id,
+	                                   NULL),
+	                NULL);
 	g_free (id);
 }
diff --git a/gitg/gitg-window.c b/gitg/gitg-window.c
index 5136d65..7e0f012 100644
--- a/gitg/gitg-window.c
+++ b/gitg/gitg-window.c
@@ -26,7 +26,6 @@
 #include <glib/gi18n.h>
 #include <libgitg/gitg-config.h>
 #include <libgitg/gitg-ref.h>
-#include <libgitg/gitg-runner.h>
 #include <libgitg/gitg-hash.h>
 
 #include "config.h"
@@ -158,31 +157,31 @@ static GtkBuildableIface parent_iface;
 static GtkWindowClass *parent_class = NULL;
 
 static void
-on_branch_action_runner_end (GitgRunner *runner,
-                             gboolean    cancelled,
-                             GitgWindow *window)
+on_branch_action_shell_end (GitgShell *shell,
+                            gboolean    cancelled,
+                            GitgWindow *window)
 {
-	window->priv->branch_actions = g_list_remove (window->priv->branch_actions, runner);
-	g_object_unref (runner);
+	window->priv->branch_actions = g_list_remove (window->priv->branch_actions, shell);
+	g_object_unref (shell);
 }
 
 gboolean
 gitg_window_add_branch_action (GitgWindow *window,
-                               GitgRunner *runner)
+                               GitgShell  *shell)
 {
-	if (runner != NULL && gitg_runner_running (runner))
+	if (shell != NULL && gitg_io_get_running (GITG_IO (shell)))
 	{
-		window->priv->branch_actions = g_list_prepend (window->priv->branch_actions, runner);
+		window->priv->branch_actions = g_list_prepend (window->priv->branch_actions, shell);
 
-		g_signal_connect (runner, "end-loading", G_CALLBACK (on_branch_action_runner_end), window);
+		g_signal_connect (shell, "end", G_CALLBACK (on_branch_action_shell_end), window);
 	}
-	else if (runner)
+	else if (shell)
 	{
-		g_object_unref (runner);
-		runner = NULL;
+		g_object_unref (shell);
+		shell = NULL;
 	}
 
-	return runner != NULL;
+	return shell != NULL;
 }
 
 static void
@@ -198,7 +197,7 @@ gitg_window_finalize (GObject *object)
 
 	for (item = copy; item; item = g_list_next (item))
 	{
-		gitg_runner_cancel (GITG_RUNNER (item->data));
+		gitg_io_cancel (item->data);
 	}
 
 	g_list_free (copy);
@@ -1217,7 +1216,7 @@ on_repository_loaded (GitgRepository *repository,
 }
 
 static void
-on_update (GitgRunner  *loader,
+on_update (GitgShell   *loader,
            gchar      **revisions,
            GitgWindow  *window)
 {
@@ -1782,7 +1781,7 @@ load_repository (GitgWindow   *window,
 		gtk_tree_view_set_model (window->priv->tree_view,
 		                         GTK_TREE_MODEL (window->priv->repository));
 
-		GitgRunner *loader = gitg_repository_get_loader (window->priv->repository);
+		GitgShell *loader = gitg_repository_get_loader (window->priv->repository);
 
 		gitg_window_set_select_on_load (window, selection);
 
diff --git a/gitg/gitg-window.h b/gitg/gitg-window.h
index 648f38e..3245013 100644
--- a/gitg/gitg-window.h
+++ b/gitg/gitg-window.h
@@ -25,14 +25,15 @@
 
 #include <gtk/gtk.h>
 #include <libgitg/gitg-repository.h>
+#include <libgitg/gitg-shell.h>
 
 G_BEGIN_DECLS
 
-#define GITG_TYPE_WINDOW			(gitg_window_get_type ())
-#define GITG_WINDOW(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_WINDOW, GitgWindow))
+#define GITG_TYPE_WINDOW		(gitg_window_get_type ())
+#define GITG_WINDOW(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_WINDOW, GitgWindow))
 #define GITG_WINDOW_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_WINDOW, GitgWindow const))
 #define GITG_WINDOW_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_WINDOW, GitgWindowClass))
-#define GITG_IS_WINDOW(obj)			(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_WINDOW))
+#define GITG_IS_WINDOW(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_WINDOW))
 #define GITG_IS_WINDOW_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_WINDOW))
 #define GITG_WINDOW_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_WINDOW, GitgWindowClass))
 
@@ -40,13 +41,15 @@ typedef struct _GitgWindow		GitgWindow;
 typedef struct _GitgWindowClass		GitgWindowClass;
 typedef struct _GitgWindowPrivate	GitgWindowPrivate;
 
-struct _GitgWindow {
+struct _GitgWindow
+{
 	GtkWindow parent;
 
 	GitgWindowPrivate *priv;
 };
 
-struct _GitgWindowClass {
+struct _GitgWindowClass
+{
 	GtkWindowClass parent_class;
 };
 
@@ -71,10 +74,10 @@ gboolean gitg_window_load_repository_from_environment (GitgWindow *window,
 
 void gitg_window_show_commit (GitgWindow *window);
 
-GitgRepository *gitg_window_get_repository(GitgWindow *window);
+GitgRepository *gitg_window_get_repository (GitgWindow *window);
 void gitg_window_set_select_on_load (GitgWindow *window, gchar const *selection);
 
-gboolean gitg_window_add_branch_action (GitgWindow *window, GitgRunner *runner);
+gboolean gitg_window_add_branch_action (GitgWindow *window, GitgShell *shell);
 
 gboolean gitg_window_select (GitgWindow *window, gchar const *selection);
 gboolean gitg_window_activate (GitgWindow *window, gchar const *activatable, gchar const *action);
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index cc0c2e8..a6f6572 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -30,7 +30,11 @@ INST_H_FILES =			\
 	gitg-ref.h		\
 	gitg-repository.h	\
 	gitg-revision.h		\
-	gitg-runner.h
+	gitg-runner.h		\
+	gitg-command.h		\
+	gitg-shell.h		\
+	gitg-io.h		\
+	gitg-line-parser.h
 
 NOINST_H_FILES =			\
 	gitg-convert.h			\
@@ -57,7 +61,11 @@ C_FILES =				\
 	gitg-revision.c			\
 	gitg-runner.c			\
 	gitg-smart-charset-converter.c	\
-	gitg-encodings.c
+	gitg-encodings.c		\
+	gitg-command.c			\
+	gitg-io.c			\
+	gitg-shell.c			\
+	gitg-line-parser.c
 
 ENUM_H_FILES =			\
 	gitg-changed-file.h
diff --git a/libgitg/gitg-command.c b/libgitg/gitg-command.c
new file mode 100644
index 0000000..7395b25
--- /dev/null
+++ b/libgitg/gitg-command.c
@@ -0,0 +1,491 @@
+#include "gitg-command.h"
+
+#define GITG_COMMAND_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_COMMAND, GitgCommandPrivate))
+
+#define CONST_CONST(x) ((gchar const * const *)x)
+
+struct _GitgCommandPrivate
+{
+	GitgRepository *repository;
+	gchar **arguments;
+	gchar **environment;
+	GFile *working_directory;
+};
+
+G_DEFINE_TYPE (GitgCommand, gitg_command, G_TYPE_INITIALLY_UNOWNED)
+
+enum
+{
+	PROP_0,
+	PROP_REPOSITORY,
+	PROP_ARGUMENTS,
+	PROP_ENVIRONMENT,
+	PROP_WORKING_DIRECTORY
+};
+
+static void
+gitg_command_finalize (GObject *object)
+{
+	GitgCommand *command;
+
+	command = GITG_COMMAND (object);
+
+	g_strfreev (command->priv->arguments);
+	g_strfreev (command->priv->environment);
+
+	G_OBJECT_CLASS (gitg_command_parent_class)->finalize (object);
+}
+
+static void
+gitg_command_dispose (GObject *object)
+{
+	GitgCommand *command;
+
+	command = GITG_COMMAND (object);
+
+	if (command->priv->repository != NULL)
+	{
+		g_object_unref (command->priv->repository);
+		command->priv->repository = NULL;
+	}
+
+	G_OBJECT_CLASS (gitg_command_parent_class)->dispose (object);
+}
+
+static void
+gitg_command_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+	GitgCommand *self = GITG_COMMAND (object);
+
+	switch (prop_id)
+	{
+		case PROP_REPOSITORY:
+			self->priv->repository = g_value_dup_object (value);
+			break;
+		case PROP_ARGUMENTS:
+			gitg_command_set_arguments (self,
+			                            g_value_get_boxed (value));
+			break;
+		case PROP_ENVIRONMENT:
+			gitg_command_set_environment (self,
+			                              g_value_get_boxed (value));
+			break;
+		case PROP_WORKING_DIRECTORY:
+			gitg_command_set_working_directory (self,
+			                                    g_value_get_object (value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_command_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+	GitgCommand *self = GITG_COMMAND (object);
+
+	switch (prop_id)
+	{
+		case PROP_REPOSITORY:
+			g_value_set_object (value, self->priv->repository);
+			break;
+		case PROP_ARGUMENTS:
+			g_value_set_boxed (value, self->priv->arguments);
+			break;
+		case PROP_ENVIRONMENT:
+			g_value_set_boxed (value, self->priv->environment);
+			break;
+		case PROP_WORKING_DIRECTORY:
+			g_value_take_object (value, gitg_command_get_working_directory (self));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_command_class_init (GitgCommandClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = gitg_command_finalize;
+	object_class->dispose = gitg_command_dispose;
+
+	object_class->get_property = gitg_command_get_property;
+	object_class->set_property = gitg_command_set_property;
+
+	g_type_class_add_private (object_class, sizeof(GitgCommandPrivate));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_REPOSITORY,
+	                                 g_param_spec_object ("repository",
+	                                                      "Repository",
+	                                                      "Repository",
+	                                                      GITG_TYPE_REPOSITORY,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_ARGUMENTS,
+	                                 g_param_spec_boxed ("arguments",
+	                                                     "Arguments",
+	                                                     "Arguments",
+	                                                     G_TYPE_STRV,
+	                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_ENVIRONMENT,
+	                                 g_param_spec_boxed ("environment",
+	                                                     "Environment",
+	                                                     "Environment",
+	                                                     G_TYPE_STRV,
+	                                                     G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_WORKING_DIRECTORY,
+	                                 g_param_spec_object ("working-directory",
+	                                                      "Working Directory",
+	                                                      "Working directory",
+	                                                      G_TYPE_FILE,
+	                                                      G_PARAM_READWRITE));
+}
+
+static void
+gitg_command_init (GitgCommand *self)
+{
+	self->priv = GITG_COMMAND_GET_PRIVATE (self);
+}
+
+static gchar **
+collect_arguments (va_list ap)
+{
+	GPtrArray *arguments;
+	gchar const *arg;
+
+	arguments = g_ptr_array_new ();
+
+	while ((arg = va_arg (ap, gchar const *)) != NULL)
+	{
+		g_ptr_array_add (arguments, g_strdup (arg));
+	}
+
+	g_ptr_array_add (arguments, NULL);
+
+	return (gchar **)g_ptr_array_free (arguments, FALSE);
+}
+
+static gchar **
+combine_environment (gchar const * const *environment)
+{
+	GPtrArray *ret;
+
+	ret = g_ptr_array_new ();
+
+	while (*environment)
+	{
+		gchar const *key = *environment++;
+		gchar const *value = *environment++;
+
+		gchar *combined = g_strconcat (key, "=", value, NULL);
+
+		g_ptr_array_add (ret, combined);
+	}
+
+	g_ptr_array_add (ret, NULL);
+
+	return (gchar **)g_ptr_array_free (ret, FALSE);
+}
+
+GitgCommand *
+gitg_command_new (GitgRepository      *repository,
+                  gchar const * const *arguments)
+{
+	g_return_val_if_fail (repository == NULL || GITG_IS_REPOSITORY (repository), NULL);
+
+	return g_object_new (GITG_TYPE_COMMAND,
+	                     "repository", repository,
+	                     "arguments", arguments,
+	                     NULL);
+}
+
+GitgCommand *
+gitg_command_newv (GitgRepository *repository,
+                   ...)
+{
+	va_list ap;
+	GitgCommand *ret;
+	gchar **arguments;
+
+	g_return_val_if_fail (repository == NULL || GITG_IS_REPOSITORY (repository), NULL);
+
+	va_start (ap, repository);
+
+	arguments = collect_arguments (ap);
+	ret = gitg_command_new (repository, CONST_CONST (arguments));
+
+	g_strfreev (arguments);
+	va_end (ap);
+
+	return ret;
+}
+
+GitgRepository *
+gitg_command_get_repository (GitgCommand *command)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+
+	return command->priv->repository;
+}
+
+void
+gitg_command_set_arguments (GitgCommand         *command,
+                            gchar const * const *arguments)
+{
+	GPtrArray *ret;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	ret = g_ptr_array_new ();
+
+	if (command->priv->repository)
+	{
+		GFile *git_dir;
+		GFile *work_tree;
+
+		gchar *git_dir_path;
+		gchar *work_tree_path;
+
+		git_dir = gitg_repository_get_git_dir (command->priv->repository);
+		work_tree = gitg_repository_get_work_tree (command->priv->repository);
+
+		git_dir_path = g_file_get_path (git_dir);
+		work_tree_path = g_file_get_path (work_tree);
+
+		g_object_unref (git_dir);
+		g_object_unref (work_tree);
+
+		g_ptr_array_add (ret, g_strdup ("git"));
+		g_ptr_array_add (ret, g_strdup ("--git-dir"));
+		g_ptr_array_add (ret, git_dir_path);
+		g_ptr_array_add (ret, g_strdup ("--work-tree"));
+		g_ptr_array_add (ret, work_tree_path);
+	}
+
+	while (*arguments)
+	{
+		g_ptr_array_add (ret, g_strdup (*arguments++));
+	}
+
+	g_ptr_array_add (ret, NULL);
+
+	g_strfreev (command->priv->arguments);
+	command->priv->arguments = (gchar **)g_ptr_array_free (ret, FALSE);
+
+	g_object_notify (G_OBJECT (command), "arguments");
+}
+
+void
+gitg_command_set_argumentsv (GitgCommand *command,
+                             ...)
+{
+	va_list ap;
+	gchar **arguments;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	va_start (ap, command);
+	arguments = collect_arguments (ap);
+	va_end (ap);
+
+	gitg_command_set_arguments (command, CONST_CONST (arguments));
+
+	g_strfreev (arguments);
+}
+
+void
+gitg_command_add_arguments (GitgCommand         *command,
+                            gchar const * const *arguments)
+{
+	GPtrArray *args;
+	gchar **ptr;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	args = g_ptr_array_new ();
+
+	for (ptr = command->priv->arguments; *ptr; ++ptr)
+	{
+		g_ptr_array_add (args, *ptr);
+	}
+
+	while (*arguments)
+	{
+		g_ptr_array_add (args, g_strdup (*arguments++));
+	}
+
+	g_free (command->priv->arguments);
+
+	g_ptr_array_add (args, NULL);
+	command->priv->arguments = (gchar **)g_ptr_array_free (args, FALSE);
+
+	g_object_notify (G_OBJECT (command), "arguments");
+}
+
+void
+gitg_command_add_argumentsv (GitgCommand *command,
+                             ...)
+{
+	va_list ap;
+	gchar **arguments;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	va_start (ap, command);
+	arguments = collect_arguments (ap);
+	va_end (ap);
+
+	gitg_command_add_arguments (command, CONST_CONST (arguments));
+
+	g_strfreev (arguments);
+}
+
+gchar const * const *
+gitg_command_get_arguments (GitgCommand *command)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+	return CONST_CONST (command->priv->arguments);
+}
+
+void
+gitg_command_set_environment (GitgCommand         *command,
+                              gchar const * const *environment)
+{
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	g_strfreev (command->priv->environment);
+	command->priv->environment = combine_environment (environment);
+
+	g_object_notify (G_OBJECT (command), "environment");
+}
+
+void
+gitg_command_set_environmentv (GitgCommand *command,
+                               ...)
+{
+	va_list ap;
+	gchar **environment;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	va_start (ap, command);
+	environment = collect_arguments (ap);
+	va_end (ap);
+
+	gitg_command_set_environment (command, CONST_CONST (environment));
+
+	g_strfreev (environment);
+}
+
+void
+gitg_command_add_environment (GitgCommand         *command,
+                              gchar const * const *environment)
+{
+	GPtrArray *args;
+	gchar **combined;
+	gchar **ptr;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	args = g_ptr_array_new ();
+
+	for (ptr = command->priv->environment; *ptr; ++ptr)
+	{
+		g_ptr_array_add (args, *ptr);
+	}
+
+	combined = combine_environment (environment);
+
+	for (ptr = combined; *ptr; ++ptr)
+	{
+		g_ptr_array_add (args, *ptr);
+	}
+
+	g_free (combined);
+	g_free (command->priv->environment);
+
+	g_ptr_array_add (args, NULL);
+
+	command->priv->environment = (gchar **)g_ptr_array_free (args, FALSE);
+
+	g_object_notify (G_OBJECT (command), "arguments");
+}
+
+void
+gitg_command_add_environmentv (GitgCommand *command,
+                               ...)
+{
+	va_list ap;
+	gchar **environment;
+
+	g_return_if_fail (GITG_IS_COMMAND (command));
+
+	va_start (ap, command);
+	environment = collect_arguments (ap);
+	va_end (ap);
+
+	gitg_command_add_environment (command, CONST_CONST (environment));
+	g_strfreev (environment);
+}
+
+gchar const * const *
+gitg_command_get_environment (GitgCommand *command)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+
+	return CONST_CONST (command->priv->environment);
+}
+
+void
+gitg_command_set_working_directory (GitgCommand *command,
+                                    GFile       *working_directory)
+{
+	g_return_if_fail (GITG_IS_COMMAND (command));
+	g_return_if_fail (working_directory == NULL || G_IS_FILE (working_directory));
+
+	if (command->priv->working_directory)
+	{
+		g_object_unref (command->priv->working_directory);
+		command->priv->working_directory = NULL;
+	}
+
+	if (working_directory)
+	{
+		command->priv->working_directory = g_file_dup (working_directory);
+	}
+
+	g_object_notify (G_OBJECT (command), "working-directory");
+}
+
+GFile *
+gitg_command_get_working_directory (GitgCommand *command)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+
+	if (command->priv->working_directory)
+	{
+		return g_file_dup (command->priv->working_directory);
+	}
+	else if (command->priv->repository)
+	{
+		return gitg_repository_get_work_tree (command->priv->repository);
+	}
+
+	return NULL;
+}
diff --git a/libgitg/gitg-command.h b/libgitg/gitg-command.h
new file mode 100644
index 0000000..f31c2c6
--- /dev/null
+++ b/libgitg/gitg-command.h
@@ -0,0 +1,76 @@
+#ifndef __GITG_COMMAND_H__
+#define __GITG_COMMAND_H__
+
+#include <glib-object.h>
+#include <libgitg/gitg-repository.h>
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_COMMAND		(gitg_command_get_type ())
+#define GITG_COMMAND(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_COMMAND, GitgCommand))
+#define GITG_COMMAND_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_COMMAND, GitgCommand const))
+#define GITG_COMMAND_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_COMMAND, GitgCommandClass))
+#define GITG_IS_COMMAND(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_COMMAND))
+#define GITG_IS_COMMAND_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_COMMAND))
+#define GITG_COMMAND_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_COMMAND, GitgCommandClass))
+
+typedef struct _GitgCommand		GitgCommand;
+typedef struct _GitgCommandClass	GitgCommandClass;
+typedef struct _GitgCommandPrivate	GitgCommandPrivate;
+
+struct _GitgCommand
+{
+	/*< private >*/
+	GInitiallyUnowned parent;
+
+	GitgCommandPrivate *priv;
+
+	/*< public >*/
+};
+
+struct _GitgCommandClass
+{
+	/*< private >*/
+	GInitiallyUnownedClass parent_class;
+
+	/*< public >*/
+};
+
+GType                gitg_command_get_type              (void) G_GNUC_CONST;
+
+GitgCommand         *gitg_command_new                   (GitgRepository      *repository,
+                                                         gchar const * const *arguments);
+GitgCommand         *gitg_command_newv                  (GitgRepository      *repository,
+                                                         ...) G_GNUC_NULL_TERMINATED;
+
+GitgRepository      *gitg_command_get_repository        (GitgCommand         *command);
+
+GFile               *gitg_command_get_working_directory (GitgCommand         *command);
+void                 gitg_command_set_working_directory (GitgCommand         *command,
+                                                         GFile               *file);
+
+void                 gitg_command_set_arguments         (GitgCommand         *command,
+                                                         gchar const * const *arguments);
+void                 gitg_command_set_argumentsv        (GitgCommand         *command,
+                                                         ...) G_GNUC_NULL_TERMINATED;
+void                 gitg_command_add_arguments         (GitgCommand         *command,
+                                                         gchar const * const *arguments);
+void                 gitg_command_add_argumentsv        (GitgCommand         *command,
+                                                         ...) G_GNUC_NULL_TERMINATED;
+
+gchar const * const *gitg_command_get_arguments         (GitgCommand         *command);
+
+void                 gitg_command_set_environment       (GitgCommand         *command,
+                                                         gchar const * const *environment);
+void                 gitg_command_set_environmentv      (GitgCommand         *command,
+                                                         ...) G_GNUC_NULL_TERMINATED;
+void                 gitg_command_add_environment       (GitgCommand         *command,
+                                                         gchar const * const *environment);
+void                 gitg_command_add_environmentv      (GitgCommand         *command,
+                                                         ...) G_GNUC_NULL_TERMINATED;
+
+gchar const * const *gitg_command_get_environment       (GitgCommand         *command);
+
+G_END_DECLS
+
+#endif /* __GITG_COMMAND_H__ */
diff --git a/libgitg/gitg-commit.c b/libgitg/gitg-commit.c
index 7ad4048..12f5183 100644
--- a/libgitg/gitg-commit.c
+++ b/libgitg/gitg-commit.c
@@ -21,13 +21,13 @@
  */
 
 #include "gitg-commit.h"
-#include "gitg-runner.h"
+#include "gitg-shell.h"
 #include "gitg-changed-file.h"
 #include "gitg-config.h"
 
 #include <string.h>
 
-#define GITG_COMMIT_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_COMMIT, GitgCommitPrivate))
+#define GITG_COMMIT_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GITG_TYPE_COMMIT, GitgCommitPrivate))
 
 #define CAN_DELETE_KEY "CanDeleteKey"
 
@@ -49,7 +49,7 @@ enum
 struct _GitgCommitPrivate
 {
 	GitgRepository *repository;
-	GitgRunner *runner;
+	GitgShell *shell;
 
 	guint update_id;
 	guint end_id;
@@ -59,108 +59,124 @@ struct _GitgCommitPrivate
 
 static guint commit_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE(GitgCommit, gitg_commit, G_TYPE_OBJECT)
+G_DEFINE_TYPE (GitgCommit, gitg_commit, G_TYPE_OBJECT)
 
-static void on_changed_file_changed(GitgChangedFile *file, GitgCommit *commit);
+static void on_changed_file_changed (GitgChangedFile *file, GitgCommit *commit);
 
 GQuark
-gitg_commit_error_quark()
+gitg_commit_error_quark ()
 {
 	static GQuark quark = 0;
 
-	if (G_UNLIKELY(quark == 0))
+	if (G_UNLIKELY (quark == 0))
 		quark = g_quark_from_string ("gitg_commit_error");
 
 	return quark;
 }
 
 static void
-runner_cancel(GitgCommit *commit)
+shell_cancel (GitgCommit *commit)
 {
 	if (commit->priv->update_id)
 	{
-		g_signal_handler_disconnect(commit->priv->runner, commit->priv->update_id);
+		g_signal_handler_disconnect (commit->priv->shell,
+		                             commit->priv->update_id);
 		commit->priv->update_id = 0;
 	}
 
 	if (commit->priv->end_id)
 	{
-		g_signal_handler_disconnect(commit->priv->runner, commit->priv->end_id);
+		g_signal_handler_disconnect (commit->priv->shell,
+		                             commit->priv->end_id);
 		commit->priv->end_id = 0;
 	}
 
-	gitg_runner_cancel(commit->priv->runner);
+	gitg_io_cancel (GITG_IO (commit->priv->shell));
 }
 
 static void
-gitg_commit_finalize(GObject *object)
+gitg_commit_finalize (GObject *object)
 {
-	GitgCommit *commit = GITG_COMMIT(object);
+	GitgCommit *commit = GITG_COMMIT (object);
 
-	runner_cancel(commit);
-	g_object_unref(commit->priv->runner);
+	shell_cancel (commit);
+	g_object_unref (commit->priv->shell);
 
-	g_hash_table_destroy(commit->priv->files);
+	g_hash_table_destroy (commit->priv->files);
 
-	G_OBJECT_CLASS(gitg_commit_parent_class)->finalize(object);
+	G_OBJECT_CLASS (gitg_commit_parent_class)->finalize (object);
 }
 
 static void
-gitg_commit_dispose(GObject *object)
+gitg_commit_dispose (GObject *object)
 {
-	GitgCommit *self = GITG_COMMIT(object);
+	GitgCommit *self = GITG_COMMIT (object);
 
 	if (self->priv->repository)
 	{
-		g_signal_handlers_disconnect_by_func(self->priv->repository, G_CALLBACK(gitg_commit_refresh), self);
+		g_signal_handlers_disconnect_by_func (self->priv->repository,
+		                                      G_CALLBACK (gitg_commit_refresh),
+		                                      self);
 
-		g_object_unref(self->priv->repository);
+		g_object_unref (self->priv->repository);
 		self->priv->repository = NULL;
 	}
 }
 
 static void
-gitg_commit_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+gitg_commit_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
 {
-	GitgCommit *self = GITG_COMMIT(object);
+	GitgCommit *self = GITG_COMMIT (object);
 
 	switch (prop_id)
 	{
 		case PROP_REPOSITORY:
-			g_value_set_object(value, self->priv->repository);
-		break;
+			g_value_set_object (value, self->priv->repository);
+			break;
 		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-		break;
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
 	}
 }
 
 static void
-gitg_commit_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+gitg_commit_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
 {
-	GitgCommit *self = GITG_COMMIT(object);
+	GitgCommit *self = GITG_COMMIT (object);
 
 	switch (prop_id)
 	{
 		case PROP_REPOSITORY:
 		{
 			if (self->priv->repository)
-				g_object_unref(self->priv->repository);
+			{
+				g_object_unref (self->priv->repository);
+			}
 
-			self->priv->repository = g_value_dup_object(value);
-			g_signal_connect_swapped(self->priv->repository, "load", G_CALLBACK(gitg_commit_refresh), self);
+			self->priv->repository = g_value_dup_object (value);
+
+			g_signal_connect_swapped (self->priv->repository,
+			                          "load",
+			                          G_CALLBACK (gitg_commit_refresh),
+			                          self);
 		}
 		break;
 		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
 	}
 }
 
 static void
-gitg_commit_class_init(GitgCommitClass *klass)
+gitg_commit_class_init (GitgCommitClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = gitg_commit_dispose;
 	object_class->finalize = gitg_commit_finalize;
@@ -168,104 +184,136 @@ gitg_commit_class_init(GitgCommitClass *klass)
 	object_class->set_property = gitg_commit_set_property;
 	object_class->get_property = gitg_commit_get_property;
 
-	g_object_class_install_property(object_class, PROP_REPOSITORY,
-					 g_param_spec_object("repository",
-							      "REPOSITORY",
-							      "Repository",
-							      GITG_TYPE_REPOSITORY,
-							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+	                                 PROP_REPOSITORY,
+	                                 g_param_spec_object ("repository",
+	                                                      "REPOSITORY",
+	                                                      "Repository",
+	                                                      GITG_TYPE_REPOSITORY,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	commit_signals[INSERTED] =
-   		g_signal_new ("inserted",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (GitgCommitClass, inserted),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__OBJECT,
-			      G_TYPE_NONE,
-			      1,
-			      GITG_TYPE_CHANGED_FILE);
+		g_signal_new ("inserted",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              G_STRUCT_OFFSET (GitgCommitClass,
+		              inserted),
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__OBJECT,
+		              G_TYPE_NONE,
+		              1,
+		              GITG_TYPE_CHANGED_FILE);
 
 	commit_signals[REMOVED] =
-   		g_signal_new ("removed",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (GitgCommitClass, removed),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__OBJECT,
-			      G_TYPE_NONE,
-			      1,
-			      GITG_TYPE_CHANGED_FILE);
-
-	g_type_class_add_private(object_class, sizeof(GitgCommitPrivate));
+		g_signal_new ("removed",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              G_STRUCT_OFFSET (GitgCommitClass,
+		              removed),
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__OBJECT,
+		              G_TYPE_NONE,
+		              1,
+		              GITG_TYPE_CHANGED_FILE);
+
+	g_type_class_add_private (object_class, sizeof (GitgCommitPrivate));
 }
 
 static void
-gitg_commit_init(GitgCommit *self)
+gitg_commit_init (GitgCommit *self)
 {
-	self->priv = GITG_COMMIT_GET_PRIVATE(self);
+	self->priv = GITG_COMMIT_GET_PRIVATE (self);
 
-	self->priv->runner = gitg_runner_new(10000);
-	self->priv->files = g_hash_table_new_full(g_file_hash, (GEqualFunc)g_file_equal, (GDestroyNotify)g_object_unref, (GDestroyNotify)g_object_unref);
+	self->priv->shell = gitg_shell_new (10000);
+	self->priv->files = g_hash_table_new_full (g_file_hash,
+	                                           (GEqualFunc)g_file_equal,
+	                                           (GDestroyNotify)g_object_unref,
+	                                           (GDestroyNotify)g_object_unref);
 }
 
 GitgCommit *
-gitg_commit_new(GitgRepository *repository)
+gitg_commit_new (GitgRepository *repository)
 {
-	return g_object_new(GITG_TYPE_COMMIT, "repository", repository, NULL);
+	return g_object_new (GITG_TYPE_COMMIT, "repository", repository, NULL);
 }
 
 static void
-runner_connect(GitgCommit *commit, GCallback updatefunc, GCallback endfunc)
+shell_connect (GitgCommit *commit,
+               GCallback   updatefunc,
+               GCallback   endfunc)
 {
 	if (commit->priv->update_id)
 	{
-		g_signal_handler_disconnect(commit->priv->runner, commit->priv->update_id);
+		g_signal_handler_disconnect (commit->priv->shell,
+		                             commit->priv->update_id);
 		commit->priv->update_id = 0;
 	}
 
 	if (commit->priv->end_id)
 	{
-		g_signal_handler_disconnect(commit->priv->runner, commit->priv->end_id);
+		g_signal_handler_disconnect (commit->priv->shell,
+		                             commit->priv->end_id);
 		commit->priv->end_id = 0;
 	}
 
 	if (updatefunc)
-		commit->priv->update_id = g_signal_connect(commit->priv->runner, "update", updatefunc, commit);
+	{
+		commit->priv->update_id = g_signal_connect (commit->priv->shell,
+		                                            "update",
+		                                            updatefunc,
+		                                            commit);
+	}
 
 	if (endfunc)
-		commit->priv->end_id = g_signal_connect(commit->priv->runner, "end-loading", endfunc, commit);
+	{
+		commit->priv->end_id = g_signal_connect (commit->priv->shell,
+		                                         "end",
+		                                         endfunc,
+		                                         commit);
+	}
 }
 
 static void
-update_changed_file_status(GitgChangedFile *file, char const *action, gchar const *mode)
+update_changed_file_status (GitgChangedFile *file,
+                            char const      *action,
+                            gchar const     *mode)
 {
 	GitgChangedFileStatus status;
 
-	if (strcmp(action, "D") == 0)
+	if (strcmp (action, "D") == 0)
+	{
 		status = GITG_CHANGED_FILE_STATUS_DELETED;
-	else if (strcmp(mode, "000000") == 0)
+	}
+	else if (strcmp (mode, "000000") == 0)
+	{
 		status = GITG_CHANGED_FILE_STATUS_NEW;
+	}
 	else
+	{
 		status = GITG_CHANGED_FILE_STATUS_MODIFIED;
+	}
 
-	gitg_changed_file_set_status(file, status);
+	gitg_changed_file_set_status (file, status);
 }
 
 static void
-add_files(GitgCommit *commit, gchar **buffer, gboolean cached)
+add_files (GitgCommit  *commit,
+           gchar      **buffer,
+           gboolean     cached)
 {
 	gchar *line;
 
 	while ((line = *buffer++) != NULL)
 	{
-		gchar **parts = g_strsplit_set(line, " \t", 0);
-		guint len = g_strv_length(parts);
+		gchar **parts = g_strsplit_set (line, " \t", 0);
+		guint len = g_strv_length (parts);
 
 		if (len < 6)
 		{
-			g_warning("Invalid line: %s (%d)", line, len);
-			g_strfreev(parts);
+			g_warning ("Invalid line: %s (%d)", line, len);
+			g_strfreev (parts);
 			continue;
 		}
 
@@ -282,15 +330,15 @@ add_files(GitgCommit *commit, gchar **buffer, gboolean cached)
 
 		if (f)
 		{
-			GitgChangedFileChanges changes = gitg_changed_file_get_changes(f);
+			GitgChangedFileChanges changes = gitg_changed_file_get_changes (f);
 
-			g_object_set_data(G_OBJECT(f), CAN_DELETE_KEY, NULL);
-			update_changed_file_status(f, parts[4], mode);
+			g_object_set_data (G_OBJECT (f), CAN_DELETE_KEY, NULL);
+			update_changed_file_status (f, parts[4], mode);
 
 			if (cached)
 			{
-				gitg_changed_file_set_sha(f, sha);
-				gitg_changed_file_set_mode(f, mode);
+				gitg_changed_file_set_sha (f, sha);
+				gitg_changed_file_set_mode (f, mode);
 
 				changes |= GITG_CHANGED_FILE_CHANGES_CACHED;
 			}
@@ -299,95 +347,134 @@ add_files(GitgCommit *commit, gchar **buffer, gboolean cached)
 				changes |= GITG_CHANGED_FILE_CHANGES_UNSTAGED;
 			}
 
-			gitg_changed_file_set_changes(f, changes);
+			gitg_changed_file_set_changes (f, changes);
 
 			if ((changes & GITG_CHANGED_FILE_CHANGES_CACHED) && (changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED))
-				gitg_changed_file_set_status(f, GITG_CHANGED_FILE_STATUS_MODIFIED);
+			{
+				gitg_changed_file_set_status (f, GITG_CHANGED_FILE_STATUS_MODIFIED);
+			}
 
-			g_object_unref(file);
-			g_strfreev(parts);
+			g_object_unref (file);
+			g_strfreev (parts);
 			continue;
 		}
 
-		f = gitg_changed_file_new(file);
-		update_changed_file_status(f, parts[4], mode);
+		f = gitg_changed_file_new (file);
+		update_changed_file_status (f, parts[4], mode);
 
-		gitg_changed_file_set_sha(f, sha);
-		gitg_changed_file_set_mode(f, mode);
+		gitg_changed_file_set_sha (f, sha);
+		gitg_changed_file_set_mode (f, mode);
 
 		GitgChangedFileChanges changes;
 
 		changes = cached ? GITG_CHANGED_FILE_CHANGES_CACHED : GITG_CHANGED_FILE_CHANGES_UNSTAGED;
-		gitg_changed_file_set_changes(f, changes);
+		gitg_changed_file_set_changes (f, changes);
 
-		g_hash_table_insert(commit->priv->files, file, f);
+		g_hash_table_insert (commit->priv->files, file, f);
 
-		g_signal_connect(f, "changed", G_CALLBACK(on_changed_file_changed), commit);
-		g_signal_emit(commit, commit_signals[INSERTED], 0, f);
+		g_signal_connect (f, "changed", G_CALLBACK (on_changed_file_changed), commit);
+		g_signal_emit (commit, commit_signals[INSERTED], 0, f);
 
-		g_strfreev(parts);
+		g_strfreev (parts);
 	}
 }
 
 static void
-read_cached_files_update(GitgRunner *runner, gchar **buffer, GitgCommit *commit)
+read_cached_files_update (GitgShell   *shell,
+                          gchar      **buffer,
+                          GitgCommit  *commit)
 {
-	add_files(commit, buffer, TRUE);
+	add_files (commit, buffer, TRUE);
 }
 
 static gboolean
-delete_file(GFile *key, GitgChangedFile *value, GitgCommit *commit)
+delete_file (GFile           *key,
+             GitgChangedFile *value,
+             GitgCommit      *commit)
 {
-	if (!g_object_get_data(G_OBJECT(value), CAN_DELETE_KEY))
+	if (!g_object_get_data (G_OBJECT (value), CAN_DELETE_KEY))
+	{
 		return FALSE;
+	}
 
-	g_signal_emit(commit, commit_signals[REMOVED], 0, value);
+	g_signal_emit (commit, commit_signals[REMOVED], 0, value);
 	return TRUE;
 }
 
 static void
-refresh_done(GitgRunner *runner, gboolean cancelled, GitgCommit *commit)
+refresh_done (GitgShell  *shell,
+              gboolean    cancelled,
+              GitgCommit *commit)
 {
-	g_hash_table_foreach_remove(commit->priv->files, (GHRFunc)delete_file, commit);
+	g_hash_table_foreach_remove (commit->priv->files,
+	                             (GHRFunc)delete_file,
+	                             commit);
 }
 
 static void
-read_unstaged_files_end(GitgRunner *runner, gboolean cancelled, GitgCommit *commit)
+read_unstaged_files_end (GitgShell  *shell,
+                         gboolean    cancelled,
+                         GitgCommit *commit)
 {
-	gchar *head = gitg_repository_parse_head(commit->priv->repository);
-	gitg_runner_cancel(runner);
-
-	runner_connect(commit, G_CALLBACK(read_cached_files_update), G_CALLBACK(refresh_done));
-	gitg_repository_run_commandv(commit->priv->repository, commit->priv->runner, NULL, "diff-index", "--cached", head, NULL);
-	g_free(head);
+	gchar *head = gitg_repository_parse_head (commit->priv->repository);
+	gitg_io_cancel (GITG_IO (shell));
+
+	shell_connect (commit,
+	               G_CALLBACK (read_cached_files_update),
+	               G_CALLBACK (refresh_done));
+
+	gitg_shell_run (commit->priv->shell,
+	                gitg_command_newv (commit->priv->repository,
+	                                   "diff-index",
+	                                   "--no-ext-diff",
+	                                   "--cached",
+	                                   head,
+	                                   NULL),
+	                NULL);
+
+	g_free (head);
 }
 
 static void
-read_unstaged_files_update(GitgRunner *runner, gchar **buffer, GitgCommit *commit)
+read_unstaged_files_update (GitgShell   *shell,
+                            gchar      **buffer,
+                            GitgCommit  *commit)
 {
-	add_files(commit, buffer, FALSE);
+	add_files (commit, buffer, FALSE);
 }
 
 static void
-read_other_files_end(GitgRunner *runner, gboolean cancelled, GitgCommit *commit)
+read_other_files_end (GitgShell  *shell,
+                      gboolean    cancelled,
+                      GitgCommit *commit)
 {
-	gitg_runner_cancel(runner);
-
-	runner_connect(commit, G_CALLBACK(read_unstaged_files_update), G_CALLBACK(read_unstaged_files_end));
-	gitg_repository_run_commandv(commit->priv->repository,commit->priv->runner, NULL, "diff-files", NULL);
+	gitg_io_cancel (GITG_IO (shell));
+
+	shell_connect (commit,
+	               G_CALLBACK (read_unstaged_files_update),
+	               G_CALLBACK (read_unstaged_files_end));
+
+	gitg_shell_run (commit->priv->shell,
+	                gitg_command_newv (commit->priv->repository,
+	                                   "diff-files",
+	                                   "--no-ext-diff",
+	                                   NULL),
+	                NULL);
 }
 
 static void
-changed_file_new(GitgChangedFile *f)
+changed_file_new (GitgChangedFile *f)
 {
-	gitg_changed_file_set_status(f, GITG_CHANGED_FILE_STATUS_NEW);
-	gitg_changed_file_set_changes(f, GITG_CHANGED_FILE_CHANGES_UNSTAGED);
+	gitg_changed_file_set_status (f, GITG_CHANGED_FILE_STATUS_NEW);
+	gitg_changed_file_set_changes (f, GITG_CHANGED_FILE_CHANGES_UNSTAGED);
 
-	g_object_set_data(G_OBJECT(f), CAN_DELETE_KEY, NULL);
+	g_object_set_data (G_OBJECT (f), CAN_DELETE_KEY, NULL);
 }
 
 static void
-read_other_files_update(GitgRunner *runner, gchar **buffer, GitgCommit *commit)
+read_other_files_update (GitgShell   *shell,
+                         gchar      **buffer,
+                         GitgCommit  *commit)
 {
 	gchar *line;
 
@@ -395,7 +482,9 @@ read_other_files_update(GitgRunner *runner, gchar **buffer, GitgCommit *commit)
 	{
 		/* Skip empty lines */
 		if (!*line)
+		{
 			continue;
+		}
 
 		/* Check if file is already in our index */
 		GFile *work_tree = gitg_repository_get_work_tree (commit->priv->repository);
@@ -403,354 +492,484 @@ read_other_files_update(GitgRunner *runner, gchar **buffer, GitgCommit *commit)
 
 		g_object_unref (work_tree);
 
-		GitgChangedFile *f = g_hash_table_lookup(commit->priv->files, file);
+		GitgChangedFile *f = g_hash_table_lookup (commit->priv->files, file);
 
 		if (f)
 		{
-			changed_file_new(f);
-			g_object_unref(file);
+			changed_file_new (f);
+			g_object_unref (file);
 			continue;
 		}
 
-		f = gitg_changed_file_new(file);
+		f = gitg_changed_file_new (file);
 
-		changed_file_new(f);
-		g_hash_table_insert(commit->priv->files, file, f);
+		changed_file_new (f);
+		g_hash_table_insert (commit->priv->files, file, f);
 
-		g_signal_emit(commit, commit_signals[INSERTED], 0, f);
+		g_signal_emit (commit, commit_signals[INSERTED], 0, f);
 	}
 }
 
 static void
-update_index_end(GitgRunner *runner, gboolean cancelled, GitgCommit *commit)
+update_index_end (GitgShell  *shell,
+                  gboolean    cancelled,
+                  GitgCommit *commit)
 {
-	gitg_runner_cancel(runner);
-	runner_connect(commit, G_CALLBACK(read_other_files_update), G_CALLBACK(read_other_files_end));
-
-	gitg_repository_run_commandv(commit->priv->repository, commit->priv->runner, NULL, "ls-files", "--others", "--exclude-standard", NULL);
+	gitg_io_cancel (GITG_IO (shell));
+
+	shell_connect (commit,
+	               G_CALLBACK (read_other_files_update),
+	               G_CALLBACK (read_other_files_end));
+
+	gitg_shell_run (commit->priv->shell,
+	                gitg_command_newv (commit->priv->repository,
+	                                  "ls-files",
+	                                  "--others",
+	                                  "--exclude-standard",
+	                                  NULL),
+	                NULL);
 }
 
 static void
-update_index(GitgCommit *commit)
+update_index (GitgCommit *commit)
 {
-	runner_connect(commit, NULL, G_CALLBACK(update_index_end));
-	gitg_repository_run_commandv(commit->priv->repository, commit->priv->runner, NULL, "update-index", "-q", "--unmerged", "--ignore-missing", "--refresh", NULL);
+	shell_connect (commit,
+	               NULL,
+	               G_CALLBACK (update_index_end));
+
+	gitg_shell_run (commit->priv->shell,
+	                gitg_command_newv (commit->priv->repository,
+	                                   "update-index",
+	                                   "-q",
+	                                   "--unmerged",
+	                                   "--ignore-missing",
+	                                   "--refresh",
+	                                   NULL),
+	                NULL);
 }
 
 static void
-set_can_delete(GFile *key, GitgChangedFile *value, GitgCommit *commit)
+set_can_delete (GFile           *key,
+                GitgChangedFile *value,
+                GitgCommit      *commit)
 {
-	g_object_set_data(G_OBJECT(value), CAN_DELETE_KEY, GINT_TO_POINTER(TRUE));
-	gitg_changed_file_set_changes(value, GITG_CHANGED_FILE_CHANGES_NONE);
+	g_object_set_data (G_OBJECT (value),
+	                   CAN_DELETE_KEY,
+	                   GINT_TO_POINTER (TRUE));
+
+	gitg_changed_file_set_changes (value, GITG_CHANGED_FILE_CHANGES_NONE);
 }
 
 void
-gitg_commit_refresh(GitgCommit *commit)
+gitg_commit_refresh (GitgCommit *commit)
 {
-	g_return_if_fail(GITG_IS_COMMIT(commit));
+	g_return_if_fail (GITG_IS_COMMIT (commit));
 
-	runner_cancel(commit);
+	shell_cancel (commit);
 
-	g_hash_table_foreach(commit->priv->files, (GHFunc)set_can_delete, commit);
+	g_hash_table_foreach (commit->priv->files, (GHFunc)set_can_delete, commit);
 
 	/* Read other files */
 	if (commit->priv->repository)
-		update_index(commit);
+	{
+		update_index (commit);
+	}
 	else
-		refresh_done(commit->priv->runner, FALSE, commit);
+	{
+		refresh_done (commit->priv->shell, FALSE, commit);
+	}
 }
 
 static void
-update_index_staged(GitgCommit *commit, GitgChangedFile *file)
+update_index_staged (GitgCommit      *commit,
+                     GitgChangedFile *file)
 {
-	GFile *f = gitg_changed_file_get_file(file);
-	gchar *path = gitg_repository_relative(commit->priv->repository, f);
-	gchar *head = gitg_repository_parse_head(commit->priv->repository);
-
-	gchar **ret = gitg_repository_command_with_outputv(commit->priv->repository, NULL, "diff-index", "--cached", head, "--", path, NULL);
+	GFile *f = gitg_changed_file_get_file (file);
+	gchar *path = gitg_repository_relative (commit->priv->repository, f);
+	gchar *head = gitg_repository_parse_head (commit->priv->repository);
+
+	gchar **ret = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                                  "diff-index",
+	                                                                  "--no-ext-diff",
+	                                                                  "--cached",
+	                                                                  head,
+	                                                                  "--",
+	                                                                  path,
+	                                                                  NULL),
+	                                               FALSE,
+	                                               NULL);
 
-	g_free(path);
-	g_free(head);
-	g_object_unref(f);
+	g_free (path);
+	g_free (head);
+	g_object_unref (f);
 
 	if (!ret)
+	{
 		return;
+	}
 
-	gchar **parts = *ret ? g_strsplit_set(*ret, " \t", 0) : NULL;
-	g_strfreev(ret);
+	gchar **parts = *ret ? g_strsplit_set (*ret, " \t", 0) : NULL;
+	g_strfreev (ret);
 
-	if (parts && g_strv_length(parts) > 2)
+	if (parts && g_strv_length (parts) > 2)
 	{
-		gitg_changed_file_set_mode(file, parts[0] + 1);
-		gitg_changed_file_set_sha(file, parts[2]);
+		gitg_changed_file_set_mode (file, parts[0] + 1);
+		gitg_changed_file_set_sha (file, parts[2]);
 
-		gitg_changed_file_set_changes(file, gitg_changed_file_get_changes(file) | GITG_CHANGED_FILE_CHANGES_CACHED);
-		update_changed_file_status(file, parts[4], parts[0] + 1);
+		gitg_changed_file_set_changes (file,
+		                               gitg_changed_file_get_changes (file) |
+		                               GITG_CHANGED_FILE_CHANGES_CACHED);
+
+		update_changed_file_status (file, parts[4], parts[0] + 1);
 	}
 	else
 	{
-		gitg_changed_file_set_changes(file, gitg_changed_file_get_changes(file) & ~GITG_CHANGED_FILE_CHANGES_CACHED);
+		gitg_changed_file_set_changes (file,
+		                               gitg_changed_file_get_changes (file) &
+		                               ~GITG_CHANGED_FILE_CHANGES_CACHED);
 	}
 
 	if (parts)
-		g_strfreev(parts);
+	{
+		g_strfreev (parts);
+	}
 }
 
 static void
-update_index_unstaged(GitgCommit *commit, GitgChangedFile *file)
+update_index_unstaged (GitgCommit      *commit,
+                       GitgChangedFile *file)
 {
-	GFile *f = gitg_changed_file_get_file(file);
-	gchar *path = gitg_repository_relative(commit->priv->repository, f);
-	gchar **ret = gitg_repository_command_with_outputv(commit->priv->repository, NULL, "diff-files", "--", path, NULL);
-	g_free(path);
-	g_object_unref(f);
+	GFile *f = gitg_changed_file_get_file (file);
+	gchar *path = gitg_repository_relative (commit->priv->repository, f);
+	gchar **ret;
+
+	ret = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                          "diff-files",
+	                                                          "--no-ext-diff",
+	                                                          "--",
+	                                                          path,
+	                                                          NULL),
+	                                       FALSE,
+	                                       NULL);
+
+	g_free (path);
+	g_object_unref (f);
 
 	if (ret && *ret)
 	{
-		gitg_changed_file_set_changes(file, gitg_changed_file_get_changes(file) | GITG_CHANGED_FILE_CHANGES_UNSTAGED);
+		gitg_changed_file_set_changes (file,
+		                               gitg_changed_file_get_changes (file) |
+		                               GITG_CHANGED_FILE_CHANGES_UNSTAGED);
 	}
 	else
 	{
-		gitg_changed_file_set_changes(file, gitg_changed_file_get_changes(file) & ~GITG_CHANGED_FILE_CHANGES_UNSTAGED);
+		gitg_changed_file_set_changes (file,
+		                               gitg_changed_file_get_changes (file) &
+		                               ~GITG_CHANGED_FILE_CHANGES_UNSTAGED);
 	}
 
 	if (ret)
-		g_strfreev(ret);
+	{
+		g_strfreev (ret);
+	}
 }
 
 static void
-update_index_file(GitgCommit *commit, GitgChangedFile *file)
+update_index_file (GitgCommit      *commit,
+                   GitgChangedFile *file)
 {
 	/* update the index */
-	GFile *f = gitg_changed_file_get_file(file);
-	gchar *path = gitg_repository_relative(commit->priv->repository, f);
-	g_object_unref(f);
+	GFile *f = gitg_changed_file_get_file (file);
+	gchar *path = gitg_repository_relative (commit->priv->repository, f);
+	g_object_unref (f);
 
-	gitg_repository_commandv(commit->priv->repository, NULL, "update-index", "-q", "--unmerged", "--ignore-missing", "--refresh", NULL);
+	gitg_shell_run_sync (gitg_command_newv (commit->priv->repository,
+	                                        "update-index",
+	                                        "-q",
+	                                        "--unmerged",
+	                                        "--ignore-missing",
+	                                        "--refresh",
+	                                        NULL),
+	                     NULL);
 
-	g_free(path);
+	g_free (path);
 }
 
 static void
-refresh_changes(GitgCommit *commit, GitgChangedFile *file)
+refresh_changes (GitgCommit *commit, GitgChangedFile *file)
 {
 	/* update the index */
-	update_index_file(commit, file);
+	update_index_file (commit, file);
 
 	/* Determine if it still has staged/unstaged changes */
-	update_index_staged(commit, file);
-	update_index_unstaged(commit, file);
+	update_index_staged (commit, file);
+	update_index_unstaged (commit, file);
 
-	GitgChangedFileChanges changes = gitg_changed_file_get_changes(file);
-	GitgChangedFileStatus status = gitg_changed_file_get_status(file);
+	GitgChangedFileChanges changes = gitg_changed_file_get_changes (file);
+	GitgChangedFileStatus status = gitg_changed_file_get_status (file);
 
-	if (changes == GITG_CHANGED_FILE_CHANGES_NONE && status == GITG_CHANGED_FILE_CHANGES_NONE)
-		gitg_changed_file_set_status(file, GITG_CHANGED_FILE_STATUS_NEW);
-	else if ((changes & GITG_CHANGED_FILE_CHANGES_CACHED) && (changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED))
-		gitg_changed_file_set_status(file, GITG_CHANGED_FILE_STATUS_MODIFIED);
+	if (changes == GITG_CHANGED_FILE_CHANGES_NONE &&
+	    status == GITG_CHANGED_FILE_CHANGES_NONE)
+	{
+		gitg_changed_file_set_status (file,
+		                              GITG_CHANGED_FILE_STATUS_NEW);
+	}
+	else if ((changes & GITG_CHANGED_FILE_CHANGES_CACHED) &&
+	         (changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED))
+	{
+		gitg_changed_file_set_status (file,
+		                              GITG_CHANGED_FILE_STATUS_MODIFIED);
+	}
 
 	if (status == GITG_CHANGED_FILE_STATUS_NEW &&
-	    !(changes & GITG_CHANGED_FILE_CHANGES_CACHED))
+	    ! (changes & GITG_CHANGED_FILE_CHANGES_CACHED))
 	{
-		gitg_changed_file_set_changes(file, GITG_CHANGED_FILE_CHANGES_UNSTAGED);
+		gitg_changed_file_set_changes (file,
+		                               GITG_CHANGED_FILE_CHANGES_UNSTAGED);
 	}
 }
 
 static gboolean
-apply_hunk (GitgCommit *commit,
-            GitgChangedFile *file,
-            gchar const *hunk,
-            gboolean reverse,
-            GError **error)
+apply_hunk (GitgCommit       *commit,
+            GitgChangedFile  *file,
+            gchar const      *hunk,
+            gboolean          reverse,
+            GError          **error)
 {
-	g_return_val_if_fail (GITG_IS_COMMIT(commit), FALSE);
-	g_return_val_if_fail (GITG_IS_CHANGED_FILE(file), FALSE);
+	g_return_val_if_fail (GITG_IS_COMMIT (commit), FALSE);
+	g_return_val_if_fail (GITG_IS_CHANGED_FILE (file), FALSE);
 
 	g_return_val_if_fail (hunk != NULL, FALSE);
 
-	gboolean ret = gitg_repository_command_with_inputv (commit->priv->repository,
-	                                                    hunk,
-	                                                    error,
-	                                                    "apply",
-	                                                    "--cached",
-	                                                    reverse ? "--reverse" : NULL,
-	                                                    NULL);
+	gboolean ret = gitg_shell_run_sync_with_input (gitg_command_newv (commit->priv->repository,
+	                                                                  "apply",
+	                                                                  "--cached",
+	                                                                  reverse ? "--reverse" : NULL,
+	                                                                  NULL),
+	                                               hunk,
+	                                               error);
 
 	if (ret)
-		refresh_changes(commit, file);
+	{
+		refresh_changes (commit, file);
+	}
 
 	return ret;
 }
 
 gboolean
-gitg_commit_stage(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error)
+gitg_commit_stage (GitgCommit       *commit,
+                   GitgChangedFile  *file,
+                   gchar const      *hunk,
+                   GError          **error)
 {
 	if (hunk)
-		return apply_hunk(commit, file, hunk, FALSE, error);
+	{
+		return apply_hunk (commit, file, hunk, FALSE, error);
+	}
 
 	/* Otherwise, stage whole file */
-	GFile *f = gitg_changed_file_get_file(file);
-	gchar *path = gitg_repository_relative(commit->priv->repository, f);
-	g_object_unref(f);
+	GFile *f = gitg_changed_file_get_file (file);
+	gchar *path = gitg_repository_relative (commit->priv->repository, f);
+	g_object_unref (f);
 
-	gboolean ret = gitg_repository_commandv(commit->priv->repository, NULL, "update-index", "--add", "--remove", "--", path, NULL);
-	g_free(path);
+	gboolean ret = gitg_shell_run_sync (gitg_command_newv (commit->priv->repository,
+	                                                       "update-index",
+	                                                       "--add",
+	                                                       "--remove",
+	                                                       "--",
+	                                                       path,
+	                                                       NULL),
+	                                    error);
+	g_free (path);
 
 	if (ret)
-		refresh_changes(commit, file);
+	{
+		refresh_changes (commit, file);
+	}
 	else
-		g_error("Update index for stage failed");
+	{
+		g_error ("Update index for stage failed");
+	}
 
 	return ret;
 }
 
 gboolean
-gitg_commit_unstage(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error)
+gitg_commit_unstage (GitgCommit       *commit,
+                     GitgChangedFile  *file,
+                     gchar const      *hunk,
+                     GError          **error)
 {
 	if (hunk)
-		return apply_hunk(commit, file, hunk, TRUE, error);
+	{
+		return apply_hunk (commit, file, hunk, TRUE, error);
+	}
 
 	/* Otherwise, unstage whole file */
-	GFile *f = gitg_changed_file_get_file(file);
-	gchar *path = gitg_repository_relative(commit->priv->repository, f);
-	g_object_unref(f);
+	GFile *f = gitg_changed_file_get_file (file);
+	gchar *path = gitg_repository_relative (commit->priv->repository, f);
+	g_object_unref (f);
 
-	gchar *input = g_strdup_printf("%s %s\t%s\n", gitg_changed_file_get_mode(file), gitg_changed_file_get_sha(file), path);
-	gboolean ret = gitg_repository_command_with_inputv(commit->priv->repository, input, error, "update-index", "--index-info", NULL);
-	g_free(input);
+	gchar *input = g_strdup_printf ("%s %s\t%s\n",
+	                                gitg_changed_file_get_mode (file),
+	                                gitg_changed_file_get_sha (file),
+	                                path);
+
+	gboolean ret = gitg_shell_run_sync_with_input (gitg_command_newv (commit->priv->repository,
+	                                                                  "update-index",
+	                                                                  "--index-info",
+	                                                                  NULL),
+	                                               input,
+	                                               error);
+
+	g_free (input);
 
 	if (ret)
-		refresh_changes(commit, file);
+	{
+		refresh_changes (commit, file);
+	}
 	else
-		g_error("Update index for unstage failed");
+	{
+		g_error ("Update index for unstage failed");
+	}
 
 	return ret;
 }
 
 static void
-find_staged(GFile *key, GitgChangedFile *value, gboolean *result)
+find_staged (GFile           *key,
+             GitgChangedFile *value,
+             gboolean        *result)
 {
 	if (*result)
+	{
 		return;
+	}
 
-	*result = (gitg_changed_file_get_changes(value) & GITG_CHANGED_FILE_CHANGES_CACHED);
+	*result = (gitg_changed_file_get_changes (value) &
+	           GITG_CHANGED_FILE_CHANGES_CACHED);
 }
 
 gboolean
-gitg_commit_has_changes(GitgCommit *commit)
+gitg_commit_has_changes (GitgCommit *commit)
 {
-	g_return_val_if_fail(GITG_IS_COMMIT(commit), FALSE);
+	g_return_val_if_fail (GITG_IS_COMMIT (commit), FALSE);
 	gboolean result = FALSE;
 
-	g_hash_table_foreach(commit->priv->files, (GHFunc)find_staged, &result);
+	g_hash_table_foreach (commit->priv->files, (GHFunc)find_staged, &result);
 	return result;
 }
 
 static gchar *
-comment_parse_subject(gchar const *comment)
+comment_parse_subject (gchar const *comment)
 {
 	gchar *ptr;
 	gchar *subject;
 
-	if ((ptr = g_utf8_strchr(comment, g_utf8_strlen(comment, -1), '\n')) != NULL)
+	if ((ptr = g_utf8_strchr (comment, g_utf8_strlen (comment, -1), '\n')) != NULL)
 	{
-		subject = g_strndup(comment, ptr - comment);
+		subject = g_strndup (comment, ptr - comment);
 	}
 	else
 	{
-		subject = g_strdup(comment);
+		subject = g_strdup (comment);
 	}
 
-	gchar *commit = g_strconcat("commit:", subject, NULL);
-	g_free(subject);
+	gchar *commit = g_strconcat ("commit:", subject, NULL);
+	g_free (subject);
 
 	return commit;
 }
 
 static gboolean
-write_tree(GitgCommit *commit, gchar **tree, GError **error)
+write_tree (GitgCommit *commit, gchar **tree, GError **error)
 {
-	gchar const *argv[] = {"write-tree", NULL};
-	gchar **lines = gitg_repository_command_with_output(commit->priv->repository, argv, error);
+	gchar **lines = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                                    "write-tree",
+	                                                                    NULL),
+	                                                 FALSE,
+	                                                 error);
 
-	if (!lines || strlen(*lines) != GITG_HASH_SHA_SIZE)
+	if (!lines || strlen (*lines) != GITG_HASH_SHA_SIZE)
 	{
-		g_strfreev(lines);
+		g_strfreev (lines);
 		return FALSE;
 	}
 
-	*tree = g_strdup(*lines);
-	g_strfreev(lines);
+	*tree = g_strdup (*lines);
+	g_strfreev (lines);
 
 	return TRUE;
 }
 
 static gchar *
-get_signed_off_line(GitgCommit *commit)
+get_signed_off_line (GitgCommit *commit)
 {
-	gchar **user = gitg_repository_command_with_outputv(commit->priv->repository, NULL, "config", "--get", "user.name", NULL);
+	gchar **user = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                                   "config",
+	                                                                   "--get",
+	                                                                   "user.name",
+	                                                                   NULL),
+	                                                FALSE,
+	                                                NULL);
 
 	if (!user)
+	{
 		return NULL;
+	}
 
 	if (!*user || !**user)
 	{
-		g_strfreev(user);
+		g_strfreev (user);
 		return NULL;
 	}
 
-	gchar **email = gitg_repository_command_with_outputv(commit->priv->repository, NULL, "config", "--get", "user.email", NULL);
+	gchar **email = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                                    "config",
+	                                                                    "--get",
+	                                                                    "user.email",
+	                                                                    NULL),
+	                                                 FALSE,
+	                                                 NULL);
 
 	if (!email)
 	{
-		g_strfreev(user);
+		g_strfreev (user);
 		return NULL;
 	}
 
 	if (!*email || !**email)
 	{
-		g_strfreev(user);
-		g_strfreev(email);
+		g_strfreev (user);
+		g_strfreev (email);
 
 		return NULL;
 	}
 
-	gchar *ret = g_strdup_printf("Signed-off-by: %s <%s>", *user, *email);
-	g_strfreev(user);
-	g_strfreev(email);
+	gchar *ret = g_strdup_printf ("Signed-off-by: %s <%s>", *user, *email);
+	g_strfreev (user);
+	g_strfreev (email);
 
 	return ret;
 }
 
 static void
-on_commit_tree_update (GitgRunner *runner, gchar **lines, GString *buffer)
-{
-	while (lines && *lines)
-	{
-		if (buffer->len != 0)
-		{
-			g_string_append_c (buffer, '\n');
-		}
-
-		g_string_append (buffer, *lines);
-		++lines;
-	}
-}
-
-static void
-set_amend_environment (GitgCommit *commit, GitgRunner *runner)
+set_amend_environment (GitgCommit  *commit,
+                       GitgCommand *command)
 {
 	gchar **out;
 
-	out = gitg_repository_command_with_outputv (commit->priv->repository,
-	                                            NULL,
-	                                            "cat-file",
-	                                            "commit",
-	                                            "HEAD",
-	                                            NULL);
+	out = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                          "cat-file",
+	                                                          "commit",
+	                                                          "HEAD",
+	                                                          NULL),
+	                                       FALSE,
+	                                       NULL);
 
 	// Parse author
-	GRegex *r = g_regex_new ("^author (.*) <([^>]*)> ([0-9]+.*)$",
+	GRegex *r = g_regex_new ("^author (.*) < ([^>]*)> ([0-9]+.*)$",
 	                         G_REGEX_CASELESS,
 	                         0,
 	                         NULL);
@@ -766,9 +985,9 @@ set_amend_environment (GitgCommit *commit, GitgRunner *runner)
 			gchar *email = g_match_info_fetch (info, 2);
 			gchar *date = g_match_info_fetch (info, 3);
 
-			gitg_runner_add_environment (runner, "GIT_AUTHOR_NAME", name);
-			gitg_runner_add_environment (runner, "GIT_AUTHOR_EMAIL", email);
-			gitg_runner_add_environment (runner, "GIT_AUTHOR_DATE", date);
+			gitg_command_add_environmentv (command, "GIT_AUTHOR_NAME", name, NULL);
+			gitg_command_add_environmentv (command, "GIT_AUTHOR_EMAIL", email, NULL);
+			gitg_command_add_environmentv (command, "GIT_AUTHOR_DATE", date, NULL);
 
 			g_free (name);
 			g_free (email);
@@ -784,7 +1003,8 @@ set_amend_environment (GitgCommit *commit, GitgRunner *runner)
 }
 
 static gchar *
-convert_commit_encoding (GitgCommit *commit, gchar const *s)
+convert_commit_encoding (GitgCommit  *commit,
+                         gchar const *s)
 {
 	GitgConfig *config;
 	gchar *encoding;
@@ -824,98 +1044,129 @@ convert_commit_encoding (GitgCommit *commit, gchar const *s)
 	return ret;
 }
 
-static gboolean 
-commit_tree(GitgCommit *commit, gchar const *tree, gchar const *comment, gboolean signoff, gboolean amend, gchar **ref, GError **error)
+static gboolean
+commit_tree (GitgCommit   *commit,
+             gchar const  *tree,
+             gchar const  *comment,
+             gboolean      signoff,
+             gboolean      amend,
+             gchar       **ref,
+             GError      **error)
 {
 	gchar *fullcomment;
 
 	if (signoff)
 	{
-		gchar *line = get_signed_off_line(commit);
+		gchar *line = get_signed_off_line (commit);
 
 		if (!line)
 		{
 			if (error)
-				g_set_error(error, GITG_COMMIT_ERROR, GITG_COMMIT_ERROR_SIGNOFF, "Could not retrieve user name or email for signoff message");
+			{
+				g_set_error (error,
+				             GITG_COMMIT_ERROR,
+				             GITG_COMMIT_ERROR_SIGNOFF,
+				             "Could not retrieve user name or email for signoff message");
+			}
 
 			return FALSE;
 		}
 
-		fullcomment = g_strconcat(comment, "\n\n", line, NULL);
+		fullcomment = g_strconcat (comment, "\n\n", line, NULL);
 	}
 	else
 	{
-		fullcomment = g_strdup(comment);
+		fullcomment = g_strdup (comment);
 	}
 
 	gchar *head;
 
 	if (amend)
 	{
-		head = gitg_repository_parse_ref(commit->priv->repository, "HEAD^");
+		head = gitg_repository_parse_ref (commit->priv->repository,
+		                                  "HEAD^");
 	}
 	else
 	{
-		head = gitg_repository_parse_ref(commit->priv->repository, "HEAD");
+		head = gitg_repository_parse_ref (commit->priv->repository,
+		                                  "HEAD");
 	}
 
-	GitgRunner *runner = gitg_runner_new_synchronized (1000);
-	GString *buffer = g_string_new ("");
+	GitgCommand *command;
+	gchar **buffer;
+
+	command = gitg_command_newv (commit->priv->repository,
+	                             "commit-tree",
+	                             tree,
+	                             head ? "-p" : NULL,
+	                             head,
+	                             NULL);
 
 	if (amend)
 	{
-		set_amend_environment (commit, runner);
+		set_amend_environment (commit, command);
 	}
 
 	gchar *converted = convert_commit_encoding (commit, fullcomment);
 
-	g_signal_connect (runner, "update", G_CALLBACK (on_commit_tree_update), buffer);
-	gitg_repository_run_command_with_inputv (commit->priv->repository,
-	                                         runner,
-	                                         converted,
-	                                         error,
-	                                         "commit-tree",
-	                                         tree,
-	                                         head ? "-p" : NULL,
-	                                         head,
-	                                         NULL);
-
-	g_free(head);
-	g_free(fullcomment);
-	g_free(converted);
-	g_object_unref (runner);
-
-	if (buffer->len != GITG_HASH_SHA_SIZE)
+	buffer = gitg_shell_run_sync_with_input_and_output (command,
+	                                                    FALSE,
+	                                                    converted,
+	                                                    error);
+
+	g_free (head);
+	g_free (fullcomment);
+	g_free (converted);
+	g_object_unref (command);
+
+	if (!buffer || !*buffer || strlen (*buffer) != GITG_HASH_SHA_SIZE)
 	{
-		g_string_free(buffer, TRUE);
+		g_strfreev (buffer);
 		return FALSE;
 	}
 
-	*ref = g_string_free (buffer, FALSE);
+	*ref = g_strdup (*buffer);
+	g_strfreev (buffer);
+
 	return TRUE;
 }
 
 static gboolean
-update_ref(GitgCommit *commit, gchar const *ref, gchar const *subject, GError **error)
+update_ref (GitgCommit   *commit,
+            gchar const  *ref,
+            gchar const  *subject,
+            GError      **error)
 {
 	gchar *converted = convert_commit_encoding (commit, subject);
-	gchar const *argv[] = {"update-ref", "-m", converted, "HEAD", ref, NULL};
 
-	gboolean ret = gitg_repository_command(commit->priv->repository, argv, error);
+	gboolean ret = gitg_shell_run_sync (gitg_command_newv (commit->priv->repository,
+	                                                       "update-ref",
+	                                                       "-m",
+	                                                       converted,
+	                                                       "HEAD",
+	                                                       ref,
+	                                                       NULL),
+	                                    error);
 	g_free (converted);
 
 	return ret;
 }
 
 gboolean
-gitg_commit_commit(GitgCommit *commit, gchar const *comment, gboolean signoff, gboolean amend, GError **error)
+gitg_commit_commit (GitgCommit   *commit,
+                    gchar const  *comment,
+                    gboolean      signoff,
+                    gboolean      amend,
+                    GError      **error)
 {
-	g_return_val_if_fail(GITG_IS_COMMIT(commit), FALSE);
+	g_return_val_if_fail (GITG_IS_COMMIT (commit), FALSE);
 
 	gchar *tree;
 
-	if (!write_tree(commit, &tree, error))
+	if (!write_tree (commit, &tree, error))
+	{
 		return FALSE;
+	}
 
 	GFile *git_dir = gitg_repository_get_git_dir (commit->priv->repository);
 	GFile *child = g_file_get_child (git_dir, "COMMIT_EDITMSG");
@@ -928,86 +1179,117 @@ gitg_commit_commit(GitgCommit *commit, gchar const *comment, gboolean signoff, g
 	g_free (path);
 
 	gchar *ref;
-	gboolean ret = commit_tree(commit, tree, comment, signoff, amend, &ref, error);
-	g_free(tree);
+	gboolean ret = commit_tree (commit, tree, comment, signoff, amend, &ref, error);
+	g_free (tree);
 
 	if (!ret)
+	{
 		return FALSE;
+	}
 
-	gchar *subject = comment_parse_subject(comment);
-	ret = update_ref(commit, ref, subject, error);
-	g_free(subject);
+	gchar *subject = comment_parse_subject (comment);
+	ret = update_ref (commit, ref, subject, error);
+	g_free (subject);
 
 	if (!ret)
+	{
 		return FALSE;
+	}
 
-	gitg_repository_reload(commit->priv->repository);
+	gitg_repository_reload (commit->priv->repository);
 	return TRUE;
 }
 
 static void
-remove_file(GitgCommit *commit, GitgChangedFile *file)
+remove_file (GitgCommit      *commit,
+             GitgChangedFile *file)
 {
-	GFile *f = gitg_changed_file_get_file(file);
+	GFile *f = gitg_changed_file_get_file (file);
 
-	g_hash_table_remove(commit->priv->files, f);
-	g_object_unref(f);
+	g_hash_table_remove (commit->priv->files, f);
+	g_object_unref (f);
+
+	g_signal_emit (commit, commit_signals[REMOVED], 0, file);
+}
 
-	g_signal_emit(commit, commit_signals[REMOVED], 0, file);
+gboolean
+gitg_commit_revert (GitgCommit    *commit,
+                    GitgRevision  *from,
+                    GitgRevision  *to,
+                    GError       **error)
+{
+	g_return_val_if_fail (GITG_IS_COMMIT (commit), FALSE);
+	g_return_val_if_fail (from != NULL, FALSE);
+	g_return_val_if_fail (to != NULL, FALSE);
+
+	return FALSE;
+
+	// TODO
+	/*gchar *sha1from = gitg_revision_get_sha1 (from);
+	gchar *sha1to = gitg_revision_get_sha1 (to);
+
+	gitg_repository_command_with_outputv (commit->priv->repository,
+	                                      error,
+	                                      "diff",
+	                                      "--full-index",
+	                                      "--binary",
+	                                      "--no-color",
+	                                      sha1to,
+	                                      sha1from,
+	                                      NULL)
+	git diff --full-index --binary --no-color to from*/
 }
 
 gboolean
-gitg_commit_revert(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error)
+gitg_commit_undo (GitgCommit       *commit,
+                  GitgChangedFile  *file,
+                  gchar const      *hunk,
+                  GError          **error)
 {
 	gboolean ret;
 
 	if (!hunk)
 	{
-		GFile *f = gitg_changed_file_get_file(file);
-		gchar *path = gitg_repository_relative(commit->priv->repository, f);
-
-		ret = gitg_repository_command_with_inputv (commit->priv->repository,
-		                                           path,
-		                                           error,
-		                                           "checkout-index",
-		                                           "--index",
-		                                           "--quiet",
-		                                           "--force",
-		                                           "--stdin",
-		                                           NULL);
-
-		g_free(path);
-
-		update_index_file(commit, file);
-		update_index_unstaged(commit, file);
-		g_object_unref(f);
+		GFile *f = gitg_changed_file_get_file (file);
+		gchar *path = gitg_repository_relative (commit->priv->repository, f);
+
+		ret = gitg_shell_run_sync_with_input (gitg_command_newv (commit->priv->repository,
+		                                                         "checkout-index",
+		                                                         "--index",
+		                                                         "--quiet",
+		                                                         "--force",
+		                                                         "--stdin",
+		                                                         NULL),
+		                                       path,
+		                                       error);
+
+		g_free (path);
+
+		update_index_file (commit, file);
+		update_index_unstaged (commit, file);
+		g_object_unref (f);
 	}
 	else
 	{
-		GitgRunner *runner = gitg_runner_new_synchronized(1000);
-		gchar const *argv[] = {"patch", "-p1", "-R", NULL};
-
-		GFile *work_tree = gitg_repository_get_work_tree (commit->priv->repository);
-
-		ret = gitg_runner_run_with_arguments (runner,
-		                                      work_tree,
-		                                      argv,
+		ret = gitg_shell_run_sync_with_input (gitg_command_newv (commit->priv->repository,
+		                                                         "apply",
+		                                                         "-R",
+		                                                         "-",
+		                                                         NULL),
 		                                      hunk,
-		                                      NULL);
-
-		g_object_unref (work_tree);
-
-		update_index_file(commit, file);
-		update_index_unstaged(commit, file);
+		                                      error);
 
-		g_object_unref(runner);
+		update_index_file (commit, file);
+		update_index_unstaged (commit, file);
 	}
 
 	return ret;
 }
 
 gboolean
-gitg_commit_add_ignore (GitgCommit *commit, GitgChangedFile *file, GError **error)
+gitg_commit_add_ignore (GitgCommit       *commit,
+                        GitgChangedFile  *file,
+                        GError          **error)
 {
 	g_return_val_if_fail (GITG_IS_COMMIT (commit), FALSE);
 	g_return_val_if_fail (GITG_IS_CHANGED_FILE (file), FALSE);
@@ -1018,7 +1300,10 @@ gitg_commit_add_ignore (GitgCommit *commit, GitgChangedFile *file, GError **erro
 	GFile *git_dir = gitg_repository_get_work_tree (commit->priv->repository);
 	GFile *ignore = g_file_get_child (git_dir, ".gitignore");
 
-	GFileOutputStream *stream = g_file_append_to (ignore, G_FILE_CREATE_NONE, NULL, error);
+	GFileOutputStream *stream = g_file_append_to (ignore,
+	                                              G_FILE_CREATE_NONE,
+	                                              NULL,
+	                                              error);
 	gboolean ret = FALSE;
 
 	g_object_unref (git_dir);
@@ -1026,7 +1311,7 @@ gitg_commit_add_ignore (GitgCommit *commit, GitgChangedFile *file, GError **erro
 
 	if (stream)
 	{
-		gchar *line = g_strdup_printf("/%s\n", path);
+		gchar *line = g_strdup_printf ("/%s\n", path);
 
 		ret = g_output_stream_write_all (G_OUTPUT_STREAM (stream),
 		                                 line,
@@ -1042,7 +1327,9 @@ gitg_commit_add_ignore (GitgCommit *commit, GitgChangedFile *file, GError **erro
 	}
 
 	if (ret)
+	{
 		remove_file (commit, file);
+	}
 
 	g_object_unref (f);
 	g_free (path);
@@ -1051,22 +1338,24 @@ gitg_commit_add_ignore (GitgCommit *commit, GitgChangedFile *file, GError **erro
 }
 
 static void
-on_changed_file_changed(GitgChangedFile *file, GitgCommit *commit)
+on_changed_file_changed (GitgChangedFile *file,
+                         GitgCommit      *commit)
 {
-	refresh_changes(commit, file);
+	refresh_changes (commit, file);
 }
 
 GitgChangedFile *
-gitg_commit_find_changed_file(GitgCommit *commit, GFile *file)
+gitg_commit_find_changed_file (GitgCommit *commit,
+                               GFile      *file)
 {
-	g_return_val_if_fail(GITG_IS_COMMIT(commit), NULL);
-	g_return_val_if_fail(G_IS_FILE(file), NULL);
+	g_return_val_if_fail (GITG_IS_COMMIT (commit), NULL);
+	g_return_val_if_fail (G_IS_FILE (file), NULL);
 
-	GitgChangedFile *f = g_hash_table_lookup(commit->priv->files, file);
+	GitgChangedFile *f = g_hash_table_lookup (commit->priv->files, file);
 
 	if (f != NULL)
 	{
-		return g_object_ref(f);
+		return g_object_ref (f);
 	}
 	else
 	{
@@ -1081,12 +1370,13 @@ gitg_commit_amend_message (GitgCommit *commit)
 
 	gchar **out;
 
-	out = gitg_repository_command_with_outputv (commit->priv->repository,
-	                                            NULL,
-	                                            "cat-file",
-	                                            "commit",
-	                                            "HEAD",
-	                                            NULL);
+	out = gitg_shell_run_sync_with_output (gitg_command_newv (commit->priv->repository,
+	                                                          "cat-file",
+	                                                          "commit",
+	                                                          "HEAD",
+	                                                          NULL),
+	                                       FALSE,
+	                                       NULL);
 
 	gchar *ret = NULL;
 
diff --git a/libgitg/gitg-commit.h b/libgitg/gitg-commit.h
index 27e2587..66a62d1 100644
--- a/libgitg/gitg-commit.h
+++ b/libgitg/gitg-commit.h
@@ -46,7 +46,8 @@ typedef struct _GitgCommitPrivate	GitgCommitPrivate;
 typedef enum
 {
 	GITG_COMMIT_ERROR_NONE = 0,
-	GITG_COMMIT_ERROR_SIGNOFF
+	GITG_COMMIT_ERROR_SIGNOFF,
+	GITG_COMMIT_ERROR_MERGE
 } GitgCommitError;
 
 struct _GitgCommit {
@@ -62,24 +63,45 @@ struct _GitgCommitClass {
 	void (*removed) (GitgCommit *commit, GitgChangedFile *file);
 };
 
-GQuark gitg_commit_error_quark(void);
-
-GType gitg_commit_get_type(void) G_GNUC_CONST;
-GitgCommit *gitg_commit_new(GitgRepository *repository);
-
-void gitg_commit_refresh(GitgCommit *commit);
-gboolean gitg_commit_stage(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error);
-gboolean gitg_commit_unstage(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error);
-
-gboolean gitg_commit_has_changes(GitgCommit *commit);
-gboolean gitg_commit_commit(GitgCommit *commit, gchar const *comment, gboolean signoff, gboolean amend, GError **error);
-
-gboolean gitg_commit_revert(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error);
-gboolean gitg_commit_add_ignore(GitgCommit *commit, GitgChangedFile *file, GError **error);
-
-GitgChangedFile *gitg_commit_find_changed_file(GitgCommit *commit, GFile *file);
-
-gchar *gitg_commit_amend_message (GitgCommit *commit);
+GQuark           gitg_commit_error_quark       (void);
+
+GType            gitg_commit_get_type          (void) G_GNUC_CONST;
+GitgCommit      *gitg_commit_new               (GitgRepository   *repository);
+
+void             gitg_commit_refresh           (GitgCommit       *commit);
+gboolean         gitg_commit_stage             (GitgCommit       *commit,
+                                                GitgChangedFile  *file,
+                                                gchar const      *hunk,
+                                                GError          **error);
+gboolean         gitg_commit_unstage           (GitgCommit       *commit,
+                                                GitgChangedFile  *file,
+                                                gchar const      *hunk,
+                                                GError          **error);
+gboolean         gitg_commit_has_changes       (GitgCommit       *commit);
+gboolean         gitg_commit_commit            (GitgCommit       *commit,
+                                                gchar const      *comment,
+                                                gboolean          signoff,
+                                                gboolean          amend,
+                                                GError          **error);
+
+gboolean         gitg_commit_revert            (GitgCommit       *commit,
+                                                GitgRevision     *from,
+                                                GitgRevision     *to,
+                                                GError          **error);
+
+gboolean         gitg_commit_undo              (GitgCommit       *commit,
+                                                GitgChangedFile  *file,
+                                                gchar const      *hunk,
+                                                GError          **error);
+
+gboolean         gitg_commit_add_ignore        (GitgCommit       *commit,
+                                                GitgChangedFile  *file,
+                                                GError          **error);
+
+GitgChangedFile *gitg_commit_find_changed_file (GitgCommit       *commit,
+                                                GFile            *file);
+
+gchar           *gitg_commit_amend_message     (GitgCommit       *commit);
 
 G_END_DECLS
 
diff --git a/libgitg/gitg-config.c b/libgitg/gitg-config.c
index 8a8b30a..e0e5934 100644
--- a/libgitg/gitg-config.c
+++ b/libgitg/gitg-config.c
@@ -21,7 +21,7 @@
  */
 
 #include "gitg-config.h"
-
+#include "gitg-shell.h"
 
 #define GITG_CONFIG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_CONFIG, GitgConfigPrivate))
 
@@ -34,7 +34,7 @@ enum
 struct _GitgConfigPrivate
 {
 	GitgRepository *repository;
-	GitgRunner *runner;
+	GitgShell *shell;
 
 	GString *accumulated;
 };
@@ -113,24 +113,22 @@ gitg_config_class_init (GitgConfigClass *klass)
 }
 
 static void
-gitg_config_accumulate (GitgRunner *runner, gchar **buffer, GitgConfig *config)
+gitg_config_accumulate (GitgShell   *shell,
+                        gchar      **buffer,
+                        GitgConfig *config)
 {
 	gchar **ptr = buffer;
 
 	while (*ptr)
 	{
-		if (config->priv->accumulated->len != 0)
-		{
-			g_string_append_c (config->priv->accumulated, '\n');
-		}
-
 		g_string_append (config->priv->accumulated, *ptr);
 		++ptr;
 	}
 }
 
 static void
-gitg_config_begin_loading (GitgRunner *runner, GitgConfig *config)
+gitg_config_begin (GitgShell  *shell,
+                   GitgConfig *config)
 {
 	g_string_erase (config->priv->accumulated, 0, -1);
 }
@@ -140,18 +138,18 @@ gitg_config_init (GitgConfig *self)
 {
 	self->priv = GITG_CONFIG_GET_PRIVATE (self);
 
-	self->priv->runner = gitg_runner_new_synchronized (1000);
+	self->priv->shell = gitg_shell_new_synchronized (1000);
 
 	self->priv->accumulated = g_string_new ("");
 
-	g_signal_connect (self->priv->runner, 
-	                  "update", 
+	g_signal_connect (self->priv->shell,
+	                  "update",
 	                  G_CALLBACK (gitg_config_accumulate),
 	                  self);
 
-	g_signal_connect (self->priv->runner, 
-	                  "begin-loading", 
-	                  G_CALLBACK (gitg_config_begin_loading),
+	g_signal_connect (self->priv->shell,
+	                  "begin",
+	                  G_CALLBACK (gitg_config_begin),
 	                  self);
 }
 
@@ -168,7 +166,8 @@ get_value_process (GitgConfig *config, gboolean ret)
 
 	if (ret)
 	{
-		res = g_strndup (config->priv->accumulated->str, config->priv->accumulated->len);
+		res = g_strndup (config->priv->accumulated->str,
+		                 config->priv->accumulated->len);
 	}
 	else
 	{
@@ -181,15 +180,14 @@ get_value_process (GitgConfig *config, gboolean ret)
 static gchar *
 get_value_global (GitgConfig *config, gchar const *key)
 {
-	gchar const *argv[] = {
-		"git",
-		"config",
-		"--global",
-		key,
-		NULL
-	};
-
-	gboolean ret = gitg_runner_run (config->priv->runner, argv, NULL);
+	gboolean ret = gitg_shell_run (config->priv->shell,
+	                               gitg_command_newv (config->priv->repository,
+	                                                  "config",
+	                                                  "--global",
+	                                                  key,
+	                                                  NULL),
+	                               NULL);
+
 	return get_value_process (config, ret);
 }
 
@@ -198,17 +196,14 @@ get_value_global_regex (GitgConfig *config,
                         gchar const *regex,
                         gchar const *value_regex)
 {
-	gchar const *argv[] = {
-		"git",
-		"config",
-		"--global",
-		"--get-regexp",
-		regex,
-		value_regex,
-		NULL
-	};
-
-	gboolean ret = gitg_runner_run (config->priv->runner, argv, NULL);
+	gboolean ret = gitg_shell_run (config->priv->shell,
+	                               gitg_command_newv (config->priv->repository,
+	                                                  "config",
+	                                                  "--global",
+	                                                  "--get-regexp",
+	                                                  NULL),
+	                               NULL);
+
 	return get_value_process (config, ret);
 }
 
@@ -225,14 +220,14 @@ get_value_local (GitgConfig *config, gchar const *key)
 	cfg_file = g_file_get_child (git_dir, "config");
 	cfg = g_file_get_path (cfg_file);
 
-	ret = gitg_repository_run_commandv (config->priv->repository, 
-	                                    config->priv->runner,
-	                                    NULL,
-	                                    "config",
-	                                    "--file",
-	                                    cfg,
-	                                    key,
-	                                    NULL);
+	ret = gitg_shell_run (config->priv->shell,
+	                      gitg_command_newv (config->priv->repository,
+	                                         "config",
+	                                         "--file",
+	                                         cfg,
+	                                         key,
+	                                         NULL),
+	                      NULL);
 
 	g_free (cfg);
 
@@ -257,16 +252,16 @@ get_value_local_regex (GitgConfig *config,
 	cfg_file = g_file_get_child (git_dir, "config");
 	cfg = g_file_get_path (cfg_file);
 
-	ret = gitg_repository_run_commandv (config->priv->repository, 
-	                                    config->priv->runner,
-	                                    NULL,
-	                                    "config",
-	                                    "--file",
-	                                    cfg,
-	                                    "--get-regexp",
-	                                    regex,
-	                                    value_regex,
-	                                    NULL);
+	ret = gitg_shell_run (config->priv->shell,
+	                      gitg_command_newv (config->priv->repository,
+	                                         "config",
+	                                         "--file",
+	                                         cfg,
+	                                         "--get-regexp",
+	                                         regex,
+	                                         value_regex,
+	                                         NULL),
+	                      NULL);
 
 	g_free (cfg);
 
@@ -279,16 +274,14 @@ get_value_local_regex (GitgConfig *config,
 static gboolean
 set_value_global (GitgConfig *config, gchar const *key, gchar const *value)
 {
-	gchar const *argv[] = {
-		"git",
-		"config",
-		"--global",
-		value == NULL ? "--unset" : key,
-		value == NULL ? key : value,
-		NULL
-	};
-
-	return gitg_runner_run (config->priv->runner, argv, NULL);
+	return gitg_shell_run (config->priv->shell,
+	                       gitg_command_newv (config->priv->repository,
+	                                          "config",
+	                                          "--global",
+	                                          value == NULL ? "--unset" : key,
+	                                          value == NULL ? key : value,
+	                                          NULL),
+	                       NULL);
 }
 
 static gboolean
@@ -304,15 +297,15 @@ set_value_local (GitgConfig *config, gchar const *key, gchar const *value)
 	cfg_file = g_file_get_child (git_dir, "config");
 	cfg = g_file_get_path (cfg_file);
 
-	ret = gitg_repository_run_commandv (config->priv->repository,
-	                                     config->priv->runner,
-	                                     NULL,
-	                                     "config",
-	                                     "--file",
-	                                     cfg,
-	                                     value == NULL ? "--unset" : key,
-	                                     value == NULL ? key : value,
-	                                     NULL);
+	ret = gitg_shell_run (config->priv->shell,
+	                      gitg_command_newv (config->priv->repository,
+	                                         "config",
+	                                         "--file",
+	                                         cfg,
+	                                         value == NULL ? "--unset" : key,
+	                                         value == NULL ? key : value,
+	                                         NULL),
+	                      NULL);
 
 	g_free (cfg);
 
@@ -325,17 +318,15 @@ set_value_local (GitgConfig *config, gchar const *key, gchar const *value)
 static gboolean
 rename_global (GitgConfig *config, gchar const *old, gchar const *nw)
 {
-	gchar const *argv[] = {
-		"git",
-		"config",
-		"--global",
-		"--rename-section",
-		old,
-		nw,
-		NULL
-	};
-
-	return gitg_runner_run (config->priv->runner, argv, NULL);
+	return gitg_shell_run (config->priv->shell,
+	                       gitg_command_newv (config->priv->repository,
+	                                          "config",
+	                                          "--global",
+	                                          "--rename-section",
+	                                          old,
+	                                          nw,
+	                                          NULL),
+	                       NULL);
 }
 
 static gboolean
@@ -351,16 +342,16 @@ rename_local (GitgConfig *config, gchar const *old, gchar const *nw)
 	cfg_file = g_file_get_child (git_dir, "config");
 	cfg = g_file_get_path (cfg_file);
 
-	ret = gitg_repository_run_commandv (config->priv->repository, 
-	                                    config->priv->runner,
-	                                    NULL,
-	                                    "config",
-	                                    "--file",
-	                                    cfg,
-	                                    "--rename-section",
-	                                    old,
-	                                    nw,
-	                                    NULL);
+	ret = gitg_shell_run (config->priv->shell,
+	                      gitg_command_newv (config->priv->repository,
+	                                         "config",
+	                                         "--file",
+	                                         cfg,
+	                                         "--rename-section",
+	                                         old,
+	                                         nw,
+	                                         NULL),
+	                      NULL);
 
 	g_free (cfg);
 
diff --git a/libgitg/gitg-config.h b/libgitg/gitg-config.h
index 0fb61d0..a68363e 100644
--- a/libgitg/gitg-config.h
+++ b/libgitg/gitg-config.h
@@ -28,36 +28,47 @@
 
 G_BEGIN_DECLS
 
-#define GITG_TYPE_CONFIG			(gitg_config_get_type ())
-#define GITG_CONFIG(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_CONFIG, GitgConfig))
+#define GITG_TYPE_CONFIG		(gitg_config_get_type ())
+#define GITG_CONFIG(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_CONFIG, GitgConfig))
 #define GITG_CONFIG_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_CONFIG, GitgConfig const))
 #define GITG_CONFIG_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_CONFIG, GitgConfigClass))
-#define GITG_IS_CONFIG(obj)			(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_CONFIG))
+#define GITG_IS_CONFIG(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_CONFIG))
 #define GITG_IS_CONFIG_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_CONFIG))
 #define GITG_CONFIG_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_CONFIG, GitgConfigClass))
 
-typedef struct _GitgConfig			GitgConfig;
+typedef struct _GitgConfig		GitgConfig;
 typedef struct _GitgConfigClass		GitgConfigClass;
 typedef struct _GitgConfigPrivate	GitgConfigPrivate;
 
-struct _GitgConfig {
+struct _GitgConfig
+{
 	GObject parent;
 
 	GitgConfigPrivate *priv;
 };
 
-struct _GitgConfigClass {
+struct _GitgConfigClass
+{
 	GObjectClass parent_class;
 };
 
-GType gitg_config_get_type (void) G_GNUC_CONST;
-GitgConfig *gitg_config_new (GitgRepository *repository);
+GType       gitg_config_get_type        (void) G_GNUC_CONST;
+GitgConfig *gitg_config_new             (GitgRepository *repository);
 
-gchar *gitg_config_get_value (GitgConfig *config, gchar const *key);
-gchar *gitg_config_get_value_regex (GitgConfig *config, gchar const *regex, gchar const *value_regex);
+gchar      *gitg_config_get_value       (GitgConfig     *config,
+                                         gchar const    *key);
 
-gboolean gitg_config_rename (GitgConfig *config, gchar const *old, gchar const *nw);
-gboolean gitg_config_set_value (GitgConfig *config, gchar const *key, gchar const *value);
+gchar      *gitg_config_get_value_regex (GitgConfig     *config,
+                                         gchar const    *regex,
+                                         gchar const    *value_regex);
+
+gboolean    gitg_config_rename          (GitgConfig     *config,
+                                         gchar const    *old,
+                                         gchar const    *nw);
+
+gboolean    gitg_config_set_value       (GitgConfig     *config,
+                                         gchar const    *key,
+                                         gchar const    *value);
 
 G_END_DECLS
 
diff --git a/libgitg/gitg-io.c b/libgitg/gitg-io.c
new file mode 100644
index 0000000..2300adb
--- /dev/null
+++ b/libgitg/gitg-io.c
@@ -0,0 +1,406 @@
+#include "gitg-io.h"
+
+
+#define GITG_IO_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_IO, GitgIOPrivate))
+
+struct _GitgIOPrivate
+{
+	GInputStream *input;
+	GOutputStream *output;
+
+	gint exit_status;
+
+	guint cancelled : 1;
+	guint running : 1;
+};
+
+enum
+{
+	PROP_0,
+
+	PROP_INPUT,
+	PROP_OUTPUT,
+	PROP_CANCELLED,
+	PROP_EXIT_STATUS,
+	PROP_RUNNING
+};
+
+enum
+{
+	BEGIN,
+	END,
+	NUM_SIGNALS
+};
+
+G_DEFINE_TYPE (GitgIO, gitg_io, G_TYPE_OBJECT)
+
+static guint signals[NUM_SIGNALS] = {0,};
+
+static void
+gitg_io_finalize (GObject *object)
+{
+	G_OBJECT_CLASS (gitg_io_parent_class)->finalize (object);
+}
+
+static void
+gitg_io_dispose (GObject *object)
+{
+	GitgIO *io;
+
+	io = GITG_IO (object);
+
+	gitg_io_close (io);
+
+	G_OBJECT_CLASS (gitg_io_parent_class)->dispose (object);
+}
+
+static void
+gitg_io_set_property (GObject      *object,
+                      guint         prop_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+	GitgIO *self = GITG_IO (object);
+
+	switch (prop_id)
+	{
+		case PROP_INPUT:
+			gitg_io_set_input (self, g_value_get_object (value));
+			break;
+		case PROP_OUTPUT:
+			gitg_io_set_output (self, g_value_get_object (value));
+			break;
+		case PROP_CANCELLED:
+			gitg_io_set_cancelled (self, g_value_get_boolean (value));
+			break;
+		case PROP_EXIT_STATUS:
+			gitg_io_set_exit_status (self, g_value_get_int (value));
+			break;
+		case PROP_RUNNING:
+			gitg_io_set_running (self, g_value_get_boolean (value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_io_get_property (GObject    *object,
+                      guint       prop_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+	GitgIO *self = GITG_IO (object);
+
+	switch (prop_id)
+	{
+		case PROP_INPUT:
+			g_value_set_object (value, self->priv->input);
+			break;
+		case PROP_OUTPUT:
+			g_value_set_object (value, self->priv->output);
+			break;
+		case PROP_CANCELLED:
+			g_value_set_boolean (value, self->priv->cancelled);
+			break;
+		case PROP_EXIT_STATUS:
+			g_value_set_int (value, self->priv->exit_status);
+			break;
+		case PROP_RUNNING:
+			g_value_set_boolean (value, self->priv->running);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_io_cancel_impl (GitgIO *io)
+{
+	io->priv->cancelled = TRUE;
+}
+
+static void
+gitg_io_begin_impl (GitgIO *io)
+{
+	gitg_io_set_running (io, TRUE);
+}
+
+static void
+gitg_io_end_impl (GitgIO *io,
+                  GError *error)
+{
+	gitg_io_set_running (io, FALSE);
+}
+
+static void
+gitg_io_class_init (GitgIOClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = gitg_io_finalize;
+	object_class->dispose = gitg_io_dispose;
+
+	object_class->get_property = gitg_io_get_property;
+	object_class->set_property = gitg_io_set_property;
+
+	klass->cancel = gitg_io_cancel_impl;
+	klass->begin = gitg_io_begin_impl;
+	klass->end = gitg_io_end_impl;
+
+	g_object_class_install_property (object_class,
+	                                 PROP_INPUT,
+	                                 g_param_spec_object ("input",
+	                                                      "Input",
+	                                                      "Input",
+	                                                      G_TYPE_INPUT_STREAM,
+	                                                      G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_OUTPUT,
+	                                 g_param_spec_object ("output",
+	                                                      "Output",
+	                                                      "Output",
+	                                                      G_TYPE_OUTPUT_STREAM,
+	                                                      G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_CANCELLED,
+	                                 g_param_spec_boolean ("cancelled",
+	                                                       "Cancelled",
+	                                                       "Cancelled",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_EXIT_STATUS,
+	                                 g_param_spec_int ("exit-status",
+	                                                   "Exit status",
+	                                                   "Exit Status",
+	                                                   G_MININT,
+	                                                   G_MAXINT,
+	                                                   0,
+	                                                   G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_RUNNING,
+	                                 g_param_spec_boolean ("running",
+	                                                       "Running",
+	                                                       "Running",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE));
+
+	signals[BEGIN] =
+		g_signal_new ("begin",
+		              G_TYPE_FROM_CLASS (klass),
+		              G_SIGNAL_RUN_FIRST,
+		              G_STRUCT_OFFSET (GitgIOClass, begin),
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__VOID,
+		              G_TYPE_NONE,
+		              0);
+
+	signals[END] =
+		g_signal_new ("end",
+		              G_TYPE_FROM_CLASS (klass),
+		              G_SIGNAL_RUN_FIRST,
+		              G_STRUCT_OFFSET (GitgIOClass, end),
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__BOXED,
+		              G_TYPE_NONE,
+		              1,
+		              G_TYPE_ERROR);
+
+	g_type_class_add_private (object_class, sizeof (GitgIOPrivate));
+}
+
+static void
+gitg_io_init (GitgIO *self)
+{
+	self->priv = GITG_IO_GET_PRIVATE (self);
+}
+
+GitgIO *
+gitg_io_new ()
+{
+	return g_object_new (GITG_TYPE_IO, NULL);
+}
+
+void
+gitg_io_begin (GitgIO *io)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (!io->priv->running)
+	{
+		g_signal_emit (io, signals[BEGIN], 0);
+	}
+}
+
+void
+gitg_io_end (GitgIO *io,
+             GError *error)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (io->priv->running)
+	{
+		g_signal_emit (io, signals[END], 0, error);
+	}
+}
+
+void
+gitg_io_cancel (GitgIO *io)
+{
+	if (GITG_IO_GET_CLASS (io)->cancel)
+	{
+		GITG_IO_GET_CLASS (io)->cancel (io);
+	}
+}
+
+gboolean
+gitg_io_get_cancelled (GitgIO *io)
+{
+	g_return_val_if_fail (GITG_IS_IO (io), FALSE);
+
+	return io->priv->cancelled;
+}
+
+void
+gitg_io_set_cancelled (GitgIO   *io,
+                       gboolean  cancelled)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (io->priv->cancelled != cancelled)
+	{
+		io->priv->cancelled = cancelled;
+		g_object_notify (G_OBJECT (io), "cancelled");
+	}
+}
+
+void
+gitg_io_set_output (GitgIO        *io,
+                    GOutputStream *stream)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+	g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+	if (io->priv->output)
+	{
+		g_object_unref (io->priv->output);
+		io->priv->output = NULL;
+	}
+
+	if (stream)
+	{
+		io->priv->output = g_object_ref (stream);
+	}
+}
+
+void
+gitg_io_set_input (GitgIO       *io,
+                   GInputStream *stream)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+	g_return_if_fail (G_IS_INPUT_STREAM (stream));
+
+	if (io->priv->input)
+	{
+		g_object_unref (io->priv->input);
+		io->priv->input = NULL;
+	}
+
+	if (stream)
+	{
+		io->priv->input = g_object_ref (stream);
+	}
+}
+
+GInputStream *
+gitg_io_get_input (GitgIO *io)
+{
+	g_return_val_if_fail (GITG_IS_IO (io), NULL);
+	return io->priv->input;
+}
+
+GOutputStream *
+gitg_io_get_output (GitgIO *io)
+{
+	g_return_val_if_fail (GITG_IS_IO (io), NULL);
+	return io->priv->output;
+}
+
+void
+gitg_io_close (GitgIO *io)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (io->priv->input)
+	{
+		g_input_stream_close (io->priv->input, NULL, NULL);
+
+		g_object_unref (io->priv->input);
+		io->priv->input = NULL;
+	}
+
+	if (io->priv->output)
+	{
+		g_output_stream_close (io->priv->output, NULL, NULL);
+
+		g_object_unref (io->priv->output);
+		io->priv->output = NULL;
+	}
+}
+
+gint
+gitg_io_get_exit_status (GitgIO *io)
+{
+	g_return_val_if_fail (GITG_IS_IO (io), 0);
+
+	return io->priv->exit_status;
+}
+
+void
+gitg_io_set_exit_status (GitgIO *io,
+                         gint    exit_status)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (io->priv->exit_status != exit_status)
+	{
+		io->priv->exit_status = exit_status;
+		g_object_notify (G_OBJECT (io), "exit-status");
+	}
+}
+
+gboolean
+gitg_io_get_running (GitgIO *io)
+{
+	g_return_val_if_fail (GITG_IS_IO (io), FALSE);
+
+	return io->priv->running;
+}
+
+void
+gitg_io_set_running (GitgIO   *io,
+                     gboolean  running)
+{
+	g_return_if_fail (GITG_IS_IO (io));
+
+	if (io->priv->running != running)
+	{
+		io->priv->running = running;
+
+		if (running)
+		{
+			io->priv->cancelled = FALSE;
+		}
+
+		g_object_notify (G_OBJECT (io), "running");
+	}
+}
diff --git a/libgitg/gitg-io.h b/libgitg/gitg-io.h
new file mode 100644
index 0000000..f240d16
--- /dev/null
+++ b/libgitg/gitg-io.h
@@ -0,0 +1,70 @@
+#ifndef __GITG_IO_H__
+#define __GITG_IO_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_IO		(gitg_io_get_type ())
+#define GITG_IO(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_IO, GitgIO))
+#define GITG_IO_CONST(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_IO, GitgIO const))
+#define GITG_IO_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_IO, GitgIOClass))
+#define GITG_IS_IO(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_IO))
+#define GITG_IS_IO_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_IO))
+#define GITG_IO_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_IO, GitgIOClass))
+
+typedef struct _GitgIO		GitgIO;
+typedef struct _GitgIOClass	GitgIOClass;
+typedef struct _GitgIOPrivate	GitgIOPrivate;
+
+struct _GitgIO
+{
+	/*< private >*/
+	GObject parent;
+
+	GitgIOPrivate *priv;
+
+	/*< public >*/
+};
+
+struct _GitgIOClass
+{
+	/*< private >*/
+	GObjectClass parent_class;
+
+	/*< public >*/
+	void (*cancel) (GitgIO *io);
+
+	/* Signals */
+	void (*begin) (GitgIO *io);
+	void (*end) (GitgIO *io, GError *error);
+};
+
+GType gitg_io_get_type (void) G_GNUC_CONST;
+GitgIO *gitg_io_new (void);
+
+void gitg_io_begin (GitgIO *io);
+void gitg_io_end (GitgIO *io, GError *error);
+
+void gitg_io_set_input (GitgIO *io, GInputStream *stream);
+void gitg_io_set_output (GitgIO *io, GOutputStream *stream);
+
+GInputStream *gitg_io_get_input (GitgIO *io);
+GOutputStream *gitg_io_get_output (GitgIO *io);
+
+void gitg_io_close (GitgIO *io);
+void gitg_io_cancel (GitgIO *io);
+
+gboolean gitg_io_get_cancelled (GitgIO *io);
+void gitg_io_set_cancelled (GitgIO *io, gboolean cancelled);
+
+gint gitg_io_get_exit_status (GitgIO *io);
+void gitg_io_set_exit_status (GitgIO *io, gint status);
+
+gboolean gitg_io_get_running (GitgIO *io);
+void gitg_io_set_running (GitgIO *io, gboolean running);
+
+G_END_DECLS
+
+#endif /* __GITG_IO_H__ */
diff --git a/libgitg/gitg-line-parser.c b/libgitg/gitg-line-parser.c
new file mode 100644
index 0000000..1efe4ca
--- /dev/null
+++ b/libgitg/gitg-line-parser.c
@@ -0,0 +1,452 @@
+#include "gitg-line-parser.h"
+
+
+#define GITG_LINE_PARSER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_LINE_PARSER, GitgLineParserPrivate))
+
+struct _GitgLineParserPrivate
+{
+	gchar *rest_buffer;
+	gsize rest_buffer_size;
+
+	gchar **lines;
+	guint buffer_size;
+
+	gchar *read_buffer;
+
+	gboolean preserve_line_endings;
+};
+
+enum
+{
+	LINES,
+	DONE,
+	NUM_SIGNALS
+};
+
+G_DEFINE_TYPE (GitgLineParser, gitg_line_parser, G_TYPE_OBJECT)
+
+enum
+{
+	PROP_0,
+	PROP_BUFFER_SIZE,
+	PROP_PRESERVE_LINE_ENDINGS
+};
+
+static guint signals[NUM_SIGNALS] = {0,};
+
+typedef struct
+{
+	GitgLineParser *parser;
+	GInputStream *stream;
+	GCancellable *cancellable;
+} AsyncData;
+
+static AsyncData *
+async_data_new (GitgLineParser *parser,
+                GInputStream   *stream,
+                GCancellable   *cancellable)
+{
+	AsyncData *data;
+
+	data = g_slice_new (AsyncData);
+	data->parser = parser;
+	data->stream = stream;
+	data->cancellable = g_object_ref (cancellable);
+
+	return data;
+}
+
+static void
+async_data_free (AsyncData *data)
+{
+	g_object_unref (data->cancellable);
+	g_slice_free (AsyncData, data);
+}
+
+static void
+free_lines (GitgLineParser *stream)
+{
+	gint i = 0;
+
+	while (stream->priv->lines[i])
+	{
+		g_free (stream->priv->lines[i++]);
+	}
+
+	stream->priv->lines[0] = NULL;
+}
+
+static void
+gitg_line_parser_finalize (GObject *object)
+{
+	GitgLineParser *stream;
+
+	stream = GITG_LINE_PARSER (object);
+
+	free_lines (stream);
+
+	g_slice_free1 (sizeof (gchar *) * (stream->priv->buffer_size + 1), stream->priv->lines);
+	g_slice_free1 (sizeof (gchar) * (stream->priv->buffer_size + 1), stream->priv->read_buffer);
+
+	G_OBJECT_CLASS (gitg_line_parser_parent_class)->finalize (object);
+}
+
+static const gchar *
+find_newline (const gchar  *ptr,
+              const gchar  *end,
+              const gchar **line_end)
+{
+
+	while (ptr < end)
+	{
+		gunichar c;
+
+		c = g_utf8_get_char (ptr);
+
+		if (c == '\n')
+		{
+			/* That's it */
+			*line_end = g_utf8_next_char (ptr);
+			return ptr;
+		}
+		else if (c == '\r')
+		{
+			gchar *next;
+
+			next = g_utf8_next_char (ptr);
+
+			if (next < end)
+			{
+				gunichar n = g_utf8_get_char (next);
+
+				if (n == '\n')
+				{
+					/* Consume both! */
+					*line_end = g_utf8_next_char (next);
+					return ptr;
+				}
+				else
+				{
+					/* That's it! */
+					*line_end = next;
+					return ptr;
+				}
+			}
+			else
+			{
+				/* Need to save it, it might come later... */
+				break;
+			}
+		}
+
+		ptr = g_utf8_next_char (ptr);
+	}
+
+	return NULL;
+}
+
+static void
+parse_lines (GitgLineParser *stream,
+             const gchar    *buffer,
+             gssize          size)
+{
+	gchar const *ptr;
+	gchar const *newline = NULL;
+	gint i = 0;
+	gchar *all = NULL;
+	gchar const *end;
+
+	if (stream->priv->rest_buffer_size > 0)
+	{
+		GString *str = g_string_sized_new (stream->priv->rest_buffer_size + size);
+
+		g_string_append_len (str, stream->priv->rest_buffer, stream->priv->rest_buffer_size);
+		g_string_append_len (str, buffer, size);
+
+		all = g_string_free (str, FALSE);
+		size += stream->priv->rest_buffer_size;
+
+		g_free (stream->priv->rest_buffer);
+		stream->priv->rest_buffer = NULL;
+		stream->priv->rest_buffer_size = 0;
+
+		ptr = all;
+	}
+	else
+	{
+		ptr = buffer;
+	}
+
+	const gchar *line_end;
+	end = ptr + size;
+
+	while ((newline = find_newline (ptr, end, &line_end)))
+	{
+		if (stream->priv->preserve_line_endings)
+		{
+			stream->priv->lines[i++] = g_strndup (ptr, line_end - ptr);
+		}
+		else
+		{
+			stream->priv->lines[i++] = g_strndup (ptr, newline - ptr);
+		}
+
+		ptr = line_end;
+
+		if (i == stream->priv->buffer_size)
+		{
+			break;
+		}
+	}
+
+	if (ptr < end)
+	{
+		stream->priv->rest_buffer_size = end - ptr;
+		stream->priv->rest_buffer = g_strndup (ptr, stream->priv->rest_buffer_size);
+	}
+
+	stream->priv->lines[i] = NULL;
+
+	g_signal_emit (stream, signals[LINES], 0, stream->priv->lines);
+
+	g_free (all);
+}
+
+static void
+emit_rest (GitgLineParser *stream)
+{
+	if (stream->priv->rest_buffer_size > 0)
+	{
+		if (!stream->priv->preserve_line_endings &&
+		     stream->priv->rest_buffer[stream->priv->rest_buffer_size - 1] == '\r')
+		{
+			stream->priv->rest_buffer[stream->priv->rest_buffer_size - 1] = '\0';
+		}
+
+		gchar *b[] = {stream->priv->rest_buffer, NULL};
+
+		g_signal_emit (stream, signals[LINES], 0, b);
+
+		g_free (stream->priv->rest_buffer);
+		stream->priv->rest_buffer = NULL;
+		stream->priv->rest_buffer_size = 0;
+	}
+}
+
+static void
+parser_done (AsyncData *data,
+             GError    *error)
+{
+	if (!error)
+	{
+		emit_rest (data->parser);
+	}
+
+	g_signal_emit (data->parser, signals[DONE], 0, error);
+
+	async_data_free (data);
+}
+
+static void
+gitg_line_parser_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+	GitgLineParser *self = GITG_LINE_PARSER (object);
+
+	switch (prop_id)
+	{
+		case PROP_BUFFER_SIZE:
+			self->priv->buffer_size = g_value_get_uint (value);
+		break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			self->priv->preserve_line_endings = g_value_get_boolean (value);
+		break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_line_parser_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+	GitgLineParser *self = GITG_LINE_PARSER (object);
+
+	switch (prop_id)
+	{
+		case PROP_BUFFER_SIZE:
+			g_value_set_uint (value, self->priv->buffer_size);
+		break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			g_value_set_boolean (value, self->priv->preserve_line_endings);
+		break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gitg_line_parser_constructed (GObject *object)
+{
+	GitgLineParser *stream;
+
+	stream = GITG_LINE_PARSER (object);
+
+	stream->priv->lines = g_slice_alloc (sizeof (gchar *) * (stream->priv->buffer_size + 1));
+	stream->priv->lines[0] = NULL;
+
+	stream->priv->read_buffer = g_slice_alloc (sizeof (gchar) * (stream->priv->buffer_size + 1));
+}
+
+static void start_read_lines (AsyncData *data);
+
+static void
+read_ready (GInputStream *stream,
+            GAsyncResult *result,
+            AsyncData    *data)
+{
+	gssize read;
+	GError *error = NULL;
+
+	read = g_input_stream_read_finish (stream, result, &error);
+
+	if (g_cancellable_is_cancelled (data->cancellable))
+	{
+		if (error)
+		{
+			g_error_free (error);
+		}
+
+		async_data_free (data);
+		return;
+	}
+
+	if (read == -1)
+	{
+		parser_done (data, error);
+
+		if (error)
+		{
+			g_error_free (error);
+		}
+	}
+	else if (read == 0)
+	{
+		parser_done (data, NULL);
+	}
+	else
+	{
+		data->parser->priv->read_buffer[read] = '\0';
+
+		parse_lines (data->parser,
+		             data->parser->priv->read_buffer,
+		             read);
+
+		start_read_lines (data);
+	}
+}
+
+static void
+start_read_lines (AsyncData *data)
+{
+	g_input_stream_read_async (data->stream,
+	                           data->parser->priv->read_buffer,
+	                           data->parser->priv->buffer_size,
+	                           G_PRIORITY_DEFAULT,
+	                           data->cancellable,
+	                           (GAsyncReadyCallback)read_ready,
+	                           data);
+}
+
+static void
+gitg_line_parser_class_init (GitgLineParserClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = gitg_line_parser_finalize;
+	object_class->constructed = gitg_line_parser_constructed;
+
+	object_class->get_property = gitg_line_parser_get_property;
+	object_class->set_property = gitg_line_parser_set_property;
+
+	g_type_class_add_private (object_class, sizeof(GitgLineParserPrivate));
+
+	signals[LINES] =
+		g_signal_new ("lines",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0,
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__POINTER,
+		              G_TYPE_NONE,
+		              1,
+		              G_TYPE_POINTER);
+
+	signals[DONE] =
+		g_signal_new ("done",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0,
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__BOXED,
+		              G_TYPE_NONE,
+		              1,
+		              G_TYPE_ERROR);
+
+	g_object_class_install_property (object_class,
+	                                 PROP_BUFFER_SIZE,
+	                                 g_param_spec_uint ("buffer-size",
+	                                                    "Buffer size",
+	                                                    "Buffer Size",
+	                                                    1,
+	                                                    G_MAXUINT,
+	                                                    100,
+	                                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_PRESERVE_LINE_ENDINGS,
+	                                 g_param_spec_boolean ("preserve-line-endings",
+	                                                       "Preserve line endings",
+	                                                       "Preserve Line Endings",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gitg_line_parser_init (GitgLineParser *self)
+{
+	self->priv = GITG_LINE_PARSER_GET_PRIVATE (self);
+}
+
+GitgLineParser *
+gitg_line_parser_new (guint    buffer_size,
+                      gboolean preserve_line_endings)
+{
+	return g_object_new (GITG_TYPE_LINE_PARSER,
+	                     "buffer-size", buffer_size,
+	                     "preserve-line-endings", preserve_line_endings,
+	                     NULL);
+}
+
+void
+gitg_line_parser_parse (GitgLineParser *parser,
+                        GInputStream   *stream,
+                        GCancellable   *cancellable)
+{
+	AsyncData *data;
+
+	g_return_if_fail (GITG_IS_LINE_PARSER (parser));
+	g_return_if_fail (G_IS_INPUT_STREAM (stream));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	data = async_data_new (parser, stream, cancellable);
+	start_read_lines (data);
+}
diff --git a/libgitg/gitg-line-parser.h b/libgitg/gitg-line-parser.h
new file mode 100644
index 0000000..5f82317
--- /dev/null
+++ b/libgitg/gitg-line-parser.h
@@ -0,0 +1,45 @@
+#ifndef __GITG_LINE_PARSER_H__
+#define __GITG_LINE_PARSER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_LINE_PARSER			(gitg_line_parser_get_type ())
+#define GITG_LINE_PARSER(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_LINE_PARSER, GitgLineParser))
+#define GITG_LINE_PARSER_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_LINE_PARSER, GitgLineParser const))
+#define GITG_LINE_PARSER_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_LINE_PARSER, GitgLineParserClass))
+#define GITG_IS_LINE_PARSER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_LINE_PARSER))
+#define GITG_IS_LINE_PARSER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_LINE_PARSER))
+#define GITG_LINE_PARSER_GET_CLASS(obj)		(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_LINE_PARSER, GitgLineParserClass))
+
+typedef struct _GitgLineParser		GitgLineParser;
+typedef struct _GitgLineParserClass	GitgLineParserClass;
+typedef struct _GitgLineParserPrivate	GitgLineParserPrivate;
+
+struct _GitgLineParser
+{
+	/*< private >*/
+	GObject parent;
+
+	GitgLineParserPrivate *priv;
+};
+
+struct _GitgLineParserClass
+{
+	/*< private >*/
+	GObjectClass parent_class;
+};
+
+GType gitg_line_parser_get_type (void) G_GNUC_CONST;
+
+GitgLineParser *gitg_line_parser_new (guint         buffer_size,
+                                      gboolean      preserve_line_endings);
+
+void gitg_line_parser_parse (GitgLineParser *parser,
+                             GInputStream   *stream,
+                             GCancellable   *cancellable);
+
+G_END_DECLS
+
+#endif /* __GITG_LINE_PARSER_H__ */
diff --git a/libgitg/gitg-repository.c b/libgitg/gitg-repository.c
index 3d4b1cd..303750a 100644
--- a/libgitg/gitg-repository.c
+++ b/libgitg/gitg-repository.c
@@ -26,6 +26,7 @@
 #include "gitg-lanes.h"
 #include "gitg-ref.h"
 #include "gitg-config.h"
+#include "gitg-shell.h"
 
 #include <gio/gio.h>
 #include <sys/time.h>
@@ -91,7 +92,7 @@ struct _GitgRepositoryPrivate
 	GFile *git_dir;
 	GFile *work_tree;
 
-	GitgRunner *loader;
+	GitgShell *loader;
 	GHashTable *hashtable;
 	gint stamp;
 	GType column_types[N_COLUMNS];
@@ -416,7 +417,7 @@ gitg_repository_finalize (GObject *object)
 	GitgRepository *rp = GITG_REPOSITORY (object);
 
 	/* Make sure to cancel the loader */
-	gitg_runner_cancel (rp->priv->loader);
+	gitg_io_cancel (GITG_IO (rp->priv->loader));
 	g_object_unref (rp->priv->loader);
 
 	g_object_unref (rp->priv->lanes);
@@ -610,16 +611,19 @@ parse_ref_intern (GitgRepository *repository,
                   gchar const    *ref,
                   gboolean        symbolic)
 {
-	gchar **ret = gitg_repository_command_with_outputv (repository,
-	                                                    NULL,
-	                                                    "rev-parse",
-	                                                    "--verify",
-	                                                    symbolic ? "--symbolic-full-name" : ref,
-	                                                    symbolic ? ref : NULL,
-	                                                    NULL);
+	gchar **ret = gitg_shell_run_sync_with_output (gitg_command_newv (repository,
+	                                                                  "rev-parse",
+	                                                                  "--verify",
+	                                                                  symbolic ? "--symbolic-full-name" : ref,
+	                                                                  symbolic ? ref : NULL,
+	                                                                  NULL),
+	                                               FALSE,
+	                                               NULL);
 
 	if (!ret)
+	{
 		return NULL;
+	}
 
 	gchar *r = g_strdup (*ret);
 	g_strfreev (ret);
@@ -748,7 +752,7 @@ gitg_repository_class_init (GitgRepositoryClass *klass)
 	                                 g_param_spec_object ("loader",
 	                                                      "LOADER",
 	                                                      "The repository loader",
-	                                                      GITG_TYPE_RUNNER,
+	                                                      GITG_TYPE_SHELL,
 	                                                      G_PARAM_READABLE));
 
 	g_object_class_install_property (object_class,
@@ -902,11 +906,11 @@ add_dummy_commit (GitgRepository *repository,
 }
 
 static void
-on_loader_end_loading (GitgRunner     *object,
-                       gboolean        cancelled,
+on_loader_end_loading (GitgShell      *object,
+                       GError         *error,
                        GitgRepository *repository)
 {
-	if (cancelled)
+	if (gitg_io_get_cancelled (GITG_IO (object)))
 	{
 		g_signal_emit (repository, repository_signals[LOADED], 0);
 		return;
@@ -931,7 +935,7 @@ on_loader_end_loading (GitgRunner     *object,
 			if (current == LOAD_STAGE_STAGED)
 			{
 				/* Check if there are unstaged changes */
-				if (show_staged && gitg_runner_get_exit_status (object) != 0)
+				if (show_staged && gitg_io_get_exit_status (GITG_IO (object)) != 0)
 				{
 					add_dummy_commit (repository, TRUE);
 				}
@@ -941,28 +945,29 @@ on_loader_end_loading (GitgRunner     *object,
 				cached = "--cached";
 			}
 
-			gitg_repository_run_commandv (repository,
-			                              object,
-			                              NULL,
-			                              "diff-index",
-			                              "--quiet",
-			                              head,
-			                              cached,
-			                              NULL);
+			gitg_shell_run (object,
+			                gitg_command_newv (repository,
+			                                   "diff-index",
+			                                   "--no-ext-diff",
+			                                   "--quiet",
+			                                   head,
+			                                   cached,
+			                                   NULL),
+			                NULL);
+
 			g_free (head);
 		}
 		break;
 		case LOAD_STAGE_UNSTAGED:
-			if (show_unstaged && gitg_runner_get_exit_status (object) != 0)
+			if (show_unstaged && gitg_io_get_exit_status (GITG_IO (object)) != 0)
 			{
 				add_dummy_commit (repository, FALSE);
 			}
 
-			gitg_repository_run_command (repository,
-			                             object,
-			                             (gchar const **)repository->priv->last_args,
-			                             NULL);
-
+			gitg_shell_run (object,
+			                gitg_command_new (repository,
+			                                  (gchar const * const *)repository->priv->last_args),
+			                NULL);
 		break;
 		default:
 		break;
@@ -1106,7 +1111,7 @@ loader_update_commits (GitgRepository  *self,
 }
 
 static void
-on_loader_update (GitgRunner      *object,
+on_loader_update (GitgShell       *object,
                   gchar          **buffer,
                   GitgRepository  *repository)
 {
@@ -1294,7 +1299,7 @@ gitg_repository_init (GitgRepository *object)
 	                                            NULL,
 	                                            (GDestroyNotify)free_refs);
 
-	object->priv->loader = gitg_runner_new (10000);
+	object->priv->loader = gitg_shell_new (10000);
 
 	g_signal_connect (object->priv->loader,
 	                  "update",
@@ -1302,7 +1307,7 @@ gitg_repository_init (GitgRepository *object)
 	                  object);
 
 	g_signal_connect (object->priv->loader,
-	                  "end-loading",
+	                  "end",
 	                  G_CALLBACK (on_loader_end_loading),
 	                  object);
 }
@@ -1361,11 +1366,11 @@ gitg_repository_get_git_dir (GitgRepository *self)
 	return g_file_dup (self->priv->git_dir);
 }
 
-GitgRunner *
+GitgShell *
 gitg_repository_get_loader (GitgRepository *self)
 {
 	g_return_val_if_fail (GITG_IS_REPOSITORY (self), NULL);
-	return GITG_RUNNER (g_object_ref (self->priv->loader));
+	return GITG_SHELL (g_object_ref (self->priv->loader));
 }
 
 static gboolean
@@ -1382,15 +1387,15 @@ reload_revisions (GitgRepository  *repository,
 
 	repository->priv->load_stage = LOAD_STAGE_STASH;
 
-	return gitg_repository_run_commandv (repository,
-	                                     repository->priv->loader,
-	                                     error,
-	                                     "log",
-	                                     "--pretty=format:%H\x01%an\x01%ae\x01%at\x01%s",
-	                                     "--encoding=UTF-8",
-	                                     "-g",
-	                                     "refs/stash",
-	                                     NULL);
+	return gitg_shell_run (repository->priv->loader,
+	                       gitg_command_newv (repository,
+	                                          "log",
+	                                          "--pretty=format:%H\x01%an\x01%ae\x01%at\x01%s",
+	                                          "--encoding=UTF-8",
+	                                          "-g",
+	                                          "refs/stash",
+	                                          NULL),
+	                       error);
 }
 
 static gchar *
@@ -1402,7 +1407,9 @@ load_current_ref (GitgRepository *self)
 	gint numargs;
 
 	if (self->priv->last_args == NULL)
+	{
 		return NULL;
+	}
 
 	numargs = g_strv_length (self->priv->last_args);
 
@@ -1417,14 +1424,16 @@ load_current_ref (GitgRepository *self)
 		argv[2 + i] = self->priv->last_args[i];
 	}
 
-	out = gitg_repository_command_with_output (self, argv, NULL);
+	out = gitg_shell_run_sync_with_output (gitg_command_new (self, argv),
+	                                       FALSE,
+	                                       NULL);
 
 	if (!out)
 	{
 		return NULL;
 	}
 
-	if (*out && !* (out + 1))
+	if (*out && !*(out + 1))
 	{
 		ret = g_strdup (*out);
 	}
@@ -1438,12 +1447,13 @@ load_refs (GitgRepository *self)
 {
 	gchar **refs;
 
-	refs = gitg_repository_command_with_outputv (self,
-	                                             NULL,
-	                                             "for-each-ref",
-	                                             "--format=%(refname) %(objectname) %(*objectname)",
-	                                             "refs",
-	                                             NULL);
+	refs = gitg_shell_run_sync_with_output (gitg_command_newv (self,
+	                                                           "for-each-ref",
+	                                                           "--format=%(refname) %(objectname) %(*objectname)",
+	                                                           "refs",
+	                                                           NULL),
+	                                        FALSE,
+	                                        NULL);
 
 	if (!refs)
 	{
@@ -1491,7 +1501,7 @@ gitg_repository_reload (GitgRepository *repository)
 	g_return_if_fail (GITG_IS_REPOSITORY (repository));
 	g_return_if_fail (repository->priv->git_dir != NULL);
 
-	gitg_runner_cancel (repository->priv->loader);
+	gitg_io_cancel (GITG_IO (repository->priv->loader));
 
 	repository->priv->load_stage = LOAD_STAGE_NONE;
 	gitg_repository_clear (repository);
@@ -1520,7 +1530,7 @@ gitg_repository_load (GitgRepository  *self,
 		return FALSE;
 	}
 
-	gitg_runner_cancel (self->priv->loader);
+	gitg_io_cancel (GITG_IO (self->priv->loader));
 	gitg_repository_clear (self);
 
 	build_log_args (self, argc, av);
@@ -1622,6 +1632,24 @@ gitg_repository_find (GitgRepository *store,
 	                                     iter);
 }
 
+static gint
+ref_compare (GitgRef *a,
+             GitgRef *b)
+{
+	GitgRefType t1 = gitg_ref_get_ref_type (a);
+	GitgRefType t2 = gitg_ref_get_ref_type (b);
+
+	if (t1 != t2)
+	{
+		return t1 < t2 ? -1 : 1;
+	}
+	else
+	{
+		return g_strcmp0 (gitg_ref_get_shortname (a),
+		                  gitg_ref_get_shortname (b));
+	}
+}
+
 GSList *
 gitg_repository_get_refs (GitgRepository *repository)
 {
@@ -1635,13 +1663,14 @@ gitg_repository_get_refs (GitgRepository *repository)
 	{
 		GSList *val;
 
-		for (val = (GSList *)item->data; val; val = val->next)
+		for (val = item->data; val; val = val->next)
 		{
-			ret = g_slist_prepend (ret, gitg_ref_copy ( (GitgRef *)val->data));
+			ret = g_slist_insert_sorted (ret,
+			                             gitg_ref_copy (val->data),
+			                             (GCompareFunc)ref_compare);
 		}
 	}
 
-	ret = g_slist_reverse (ret);
 	g_list_free (values);
 
 	return ret;
@@ -1673,297 +1702,6 @@ gitg_repository_relative (GitgRepository *repository,
 	return g_file_get_relative_path (repository->priv->work_tree, file);
 }
 
-gboolean
-gitg_repository_run_command_with_input (GitgRepository  *repository,
-                                        GitgRunner      *runner,
-                                        gchar const    **argv,
-                                        gchar const     *input,
-                                        GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), FALSE);
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, FALSE);
-
-	guint num = g_strv_length ( (gchar **)argv);
-	guint i;
-
-	gchar const **args = g_new0 (gchar const *, num + 6);
-
-	gchar *git_dir_path = g_file_get_path (repository->priv->git_dir);
-	gchar *work_tree_path = g_file_get_path (repository->priv->work_tree);
-
-	args[0] = "git";
-	args[1] = "--git-dir";
-	args[2] = git_dir_path;
-	args[3] = "--work-tree";
-	args[4] = work_tree_path;
-
-	for (i = 0; i < num; ++i)
-	{
-		args[i + 5] = argv[i];
-	}
-
-	gboolean ret = gitg_runner_run_with_arguments (runner,
-	                                               repository->priv->work_tree,
-	                                               args,
-	                                               input,
-	                                               error);
-
-	g_free (args);
-	g_free (git_dir_path);
-	g_free (work_tree_path);
-
-	return ret;
-}
-
-gboolean
-gitg_repository_run_command (GitgRepository  *repository,
-                             GitgRunner      *runner,
-                             gchar const    **argv,
-                             GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), FALSE);
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, FALSE);
-
-	return gitg_repository_run_command_with_input (repository,
-	                                               runner,
-	                                               argv,
-	                                               NULL,
-	                                               error);
-}
-
-gboolean
-gitg_repository_command_with_input (GitgRepository  *repository,
-                                    gchar const    **argv,
-                                    gchar const     *input,
-                                    GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), FALSE);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, FALSE);
-
-	GitgRunner *runner = gitg_runner_new_synchronized (1000);
-
-	gboolean ret = gitg_repository_run_command_with_input (repository,
-	                                                       runner,
-	                                                       argv,
-	                                                       input,
-	                                                       error);
-	g_object_unref (runner);
-
-	return ret;
-}
-
-gboolean
-gitg_repository_command (GitgRepository  *repository,
-                         gchar const    **argv,
-                         GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), FALSE);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, FALSE);
-
-	return gitg_repository_command_with_input (repository,
-	                                           argv,
-	                                           NULL,
-	                                           error);
-}
-
-typedef struct
-{
-	gchar **buffer;
-	guint size;
-} CommandOutput;
-
-static void
-command_with_output_update (GitgRunner     *runner,
-                            gchar         **buffer,
-                            CommandOutput  *output)
-{
-	guint num = g_strv_length (buffer);
-	guint i;
-
-	output->buffer = g_realloc (output->buffer,
-	                            sizeof (gchar *) * (output->size + num + 1));
-
-	for (i = 0; i < num; ++i)
-	{
-		output->buffer[output->size + i] = g_strdup (buffer[i]);
-	}
-
-	output->size += num;
-	output->buffer[output->size] = NULL;
-}
-
-gchar **
-gitg_repository_command_with_input_and_output (GitgRepository  *repository,
-                                               gchar const    **argv,
-                                               gchar const     *input,
-                                               GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), NULL);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, NULL);
-
-	GitgRunner *runner = gitg_runner_new_synchronized (1000);
-	CommandOutput output = {NULL, 0};
-
-	g_signal_connect (runner, "update", G_CALLBACK (command_with_output_update), &output);
-	gboolean ret = gitg_repository_run_command_with_input (repository,
-	                                                       runner,
-	                                                       argv,
-	                                                       input,
-	                                                       error);
-
-	if (!ret)
-	{
-		g_strfreev (output.buffer);
-		output.buffer = NULL;
-	}
-
-	g_object_unref (runner);
-	return output.buffer;
-}
-
-gchar **
-gitg_repository_command_with_output (GitgRepository  *repository,
-                                     gchar const    **argv,
-                                     GError         **error)
-{
-	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), NULL);
-	g_return_val_if_fail (repository->priv->git_dir != NULL, NULL);
-
-	return gitg_repository_command_with_input_and_output (repository,
-	                                                      argv,
-	                                                      NULL,
-	                                                      error);
-}
-
-static gchar const **
-parse_valist (va_list ap)
-{
-	gchar const *a;
-	gchar const **ret = NULL;
-	guint num = 0;
-
-	while ( (a = va_arg (ap, gchar const *)) != NULL)
-	{
-		ret = g_realloc (ret, sizeof (gchar const *) * (++num + 1));
-		ret[num - 1] = a;
-	}
-
-	ret[num] = NULL;
-	return ret;
-}
-
-gboolean
-gitg_repository_commandv (GitgRepository  *repository,
-                          GError         **error,
-                          ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gboolean ret = gitg_repository_command (repository, argv, error);
-	g_free (argv);
-	return ret;
-}
-
-gboolean
-gitg_repository_command_with_inputv (GitgRepository  *repository,
-                                     gchar const     *input,
-                                     GError         **error,
-                                     ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gboolean ret = gitg_repository_command_with_input (repository,
-	                                                   argv,
-	                                                   input,
-	                                                   error);
-	g_free (argv);
-	return ret;
-}
-
-gboolean
-gitg_repository_run_commandv (GitgRepository  *repository,
-                              GitgRunner      *runner,
-                              GError         **error,
-                              ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gboolean ret = gitg_repository_run_command (repository,
-	                                            runner,
-	                                            argv,
-	                                            error);
-	g_free (argv);
-	return ret;
-}
-
-gboolean
-gitg_repository_run_command_with_inputv (GitgRepository  *repository,
-                                         GitgRunner      *runner,
-                                         gchar const     *input,
-                                         GError         **error,
-                                         ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gboolean ret = gitg_repository_run_command_with_input (repository,
-	                                                       runner,
-	                                                       argv,
-	                                                       input,
-	                                                       error);
-	g_free (argv);
-	return ret;
-}
-
-gchar **
-gitg_repository_command_with_outputv (GitgRepository  *repository,
-                                      GError         **error,
-                                      ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gchar **ret = gitg_repository_command_with_output (repository,
-	                                                   argv,
-	                                                   error);
-	g_free (argv);
-	return ret;
-}
-
-gchar **
-gitg_repository_command_with_input_and_outputv (GitgRepository  *repository,
-                                                gchar const     *input,
-                                                GError         **error,
-                                                ...)
-{
-	va_list ap;
-	va_start (ap, error);
-	gchar const **argv = parse_valist (ap);
-	va_end (ap);
-
-	gchar **ret = gitg_repository_command_with_input_and_output (repository,
-	                                                             argv,
-	                                                             input,
-	                                                             error);
-	g_free (argv);
-	return ret;
-}
-
 gchar *
 gitg_repository_parse_ref (GitgRepository *repository,
                            gchar const    *ref)
@@ -2148,8 +1886,9 @@ gboolean
 gitg_repository_get_loaded (GitgRepository *repository)
 {
 	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), FALSE);
+
 	return repository->priv->load_stage == LOAD_STAGE_LAST &&
-	       !gitg_runner_running (repository->priv->loader);
+	       !gitg_io_get_running (GITG_IO (repository->priv->loader));
 }
 
 gchar const **
diff --git a/libgitg/gitg-repository.h b/libgitg/gitg-repository.h
index 01f4ed0..158ab23 100644
--- a/libgitg/gitg-repository.h
+++ b/libgitg/gitg-repository.h
@@ -26,7 +26,6 @@
 #include <gtk/gtk.h>
 
 #include <libgitg/gitg-revision.h>
-#include <libgitg/gitg-runner.h>
 #include <libgitg/gitg-ref.h>
 
 G_BEGIN_DECLS
@@ -43,7 +42,9 @@ typedef struct _GitgRepository			GitgRepository;
 typedef struct _GitgRepositoryClass	GitgRepositoryClass;
 typedef struct _GitgRepositoryPrivate	GitgRepositoryPrivate;
 
-typedef enum 
+struct _GitgShell;
+
+typedef enum
 {
 	GITG_REPOSITORY_NO_ERROR = 0,
 	GITG_REPOSITORY_ERROR_NOT_FOUND
@@ -74,8 +75,6 @@ GFile *gitg_repository_get_git_dir (GitgRepository *repository);
 
 gboolean gitg_repository_exists (GitgRepository *repository);
 
-GitgRunner *gitg_repository_get_loader(GitgRepository *repository);
-
 gboolean gitg_repository_load(GitgRepository *repository, int argc, gchar const **argv, GError **error);
 gboolean gitg_repository_get_loaded(GitgRepository *repository);
 
@@ -93,30 +92,13 @@ GitgRef *gitg_repository_get_current_working_ref(GitgRepository *repository);
 
 gchar *gitg_repository_relative(GitgRepository *repository, GFile *file);
 
-/* Running git commands */
-gboolean gitg_repository_run_command(GitgRepository *repository, GitgRunner *runner, gchar const **argv, GError **error);
-gboolean gitg_repository_run_commandv(GitgRepository *repository, GitgRunner *runner, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-gboolean gitg_repository_run_command_with_input(GitgRepository *repository, GitgRunner *runner, gchar const **argv, gchar const *input, GError **error);
-gboolean gitg_repository_run_command_with_inputv(GitgRepository *repository, GitgRunner *runner, gchar const *input, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-gboolean gitg_repository_command_with_input(GitgRepository *repository, gchar const **argv, gchar const *input, GError **error);
-gboolean gitg_repository_command_with_inputv(GitgRepository *repository, gchar const *input, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-gboolean gitg_repository_command(GitgRepository *repository, gchar const **argv, GError **error);
-gboolean gitg_repository_commandv(GitgRepository *repository, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-gchar **gitg_repository_command_with_output(GitgRepository *repository, gchar const **argv, GError **error);
-gchar **gitg_repository_command_with_outputv(GitgRepository *repository, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-gchar **gitg_repository_command_with_input_and_output(GitgRepository *repository, gchar const **argv, gchar const *input, GError **error);
-gchar **gitg_repository_command_with_input_and_outputv(GitgRepository *repository, gchar const *input, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
 gchar *gitg_repository_parse_ref(GitgRepository *repository, gchar const *ref);
 gchar *gitg_repository_parse_head(GitgRepository *repository);
 
 void gitg_repository_reload(GitgRepository *repository);
 
+struct _GitgShell *gitg_repository_get_loader (GitgRepository *repository);
+
 gchar **gitg_repository_get_remotes (GitgRepository *repository);
 GSList const *gitg_repository_get_ref_pushes (GitgRepository *repository, GitgRef *ref);
 gchar const **gitg_repository_get_current_selection (GitgRepository *repository);
diff --git a/libgitg/gitg-runner.c b/libgitg/gitg-runner.c
index 0b4371e..dc19547 100644
--- a/libgitg/gitg-runner.c
+++ b/libgitg/gitg-runner.c
@@ -1,28 +1,6 @@
-/*
- * gitg-runner.c
- * This file is part of gitg - git repository viewer
- *
- * Copyright (C) 2009 - Jesse van den Kieboom
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#include "gitg-convert.h"
-#include "gitg-debug.h"
 #include "gitg-runner.h"
+#include "gitg-debug.h"
+
 #include "gitg-smart-charset-converter.h"
 
 #include <string.h>
@@ -31,56 +9,33 @@
 #include <errno.h>
 #include <stdlib.h>
 
-#include <gio/gio.h>
 #include <gio/gunixoutputstream.h>
 #include <gio/gunixinputstream.h>
 
 #define GITG_RUNNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_RUNNER, GitgRunnerPrivate))
 
-/* Signals */
-enum
-{
-	BEGIN_LOADING,
-	UPDATE,
-	END_LOADING,
-	LAST_SIGNAL
-};
-
-static guint runner_signals[LAST_SIGNAL] = { 0 };
-
-/* Properties */
-enum
+struct _GitgRunnerPrivate
 {
-	PROP_0,
+	GitgCommand *command;
 
-	PROP_BUFFER_SIZE,
-	PROP_SYNCHRONIZED,
-	PROP_PRESERVE_LINE_ENDINGS
-};
+	GInputStream *stdout;
+	GOutputStream *stdin;
 
-struct _GitgRunnerPrivate
-{
-	GPid pid;
-	GInputStream *input_stream;
-	GOutputStream *output_stream;
 	GCancellable *cancellable;
+	gboolean cancelled;
 
-	guint buffer_size;
-	gchar *read_buffer;
-	gchar **lines;
-	gchar **environment;
-
-	gchar *rest_buffer;
-	gssize rest_buffer_size;
+	GPid pid;
+	guint watch_id;
+};
 
-	gint exit_status;
+G_DEFINE_TYPE (GitgRunner, gitg_runner, GITG_TYPE_IO)
 
-	guint synchronized : 1;
-	guint preserve_line_endings : 1;
+enum
+{
+	PROP_0,
+	PROP_COMMAND
 };
 
-G_DEFINE_TYPE (GitgRunner, gitg_runner, G_TYPE_OBJECT)
-
 typedef struct
 {
 	GitgRunner *runner;
@@ -88,12 +43,14 @@ typedef struct
 } AsyncData;
 
 static AsyncData *
-async_data_new (GitgRunner   *runner,
-                GCancellable *cancellable)
+async_data_new (GitgRunner *runner)
 {
-	AsyncData *data = g_slice_new (AsyncData);
+	AsyncData *data;
+
+	data = g_slice_new (AsyncData);
+
 	data->runner = runner;
-	data->cancellable = g_object_ref (cancellable);
+	data->cancellable = g_object_ref (runner->priv->cancellable);
 
 	return data;
 }
@@ -105,587 +62,257 @@ async_data_free (AsyncData *data)
 	g_slice_free (AsyncData, data);
 }
 
-GQuark
-gitg_runner_error_quark (void)
+static void
+gitg_runner_finalize (GObject *object)
 {
-	static GQuark quark = 0;
-
-	if (G_UNLIKELY (quark == 0))
-	{
-		quark = g_quark_from_string ("gitg_runner_error");
-	}
-
-	return quark;
+	G_OBJECT_CLASS (gitg_runner_parent_class)->finalize (object);
 }
 
 static void
-runner_io_exit (GPid        pid,
-                gint        status,
-                GitgRunner *runner)
+close_streams (GitgRunner *runner)
 {
-	g_spawn_close_pid (pid);
-
-	if (runner->priv->pid)
+	if (runner->priv->cancellable)
 	{
-		runner->priv->pid = 0;
-		runner->priv->exit_status = status;
+		g_cancellable_cancel (runner->priv->cancellable);
 	}
-}
 
-static void
-free_lines (GitgRunner *runner)
-{
-	gint i = 0;
+	if (runner->priv->stdin != NULL)
+	{
+		g_output_stream_close (runner->priv->stdin, NULL, NULL);
+		g_object_unref (runner->priv->stdin);
+
+		runner->priv->stdin = NULL;
+	}
 
-	while (runner->priv->lines[i])
+	if (runner->priv->stdout != NULL)
 	{
-		g_free (runner->priv->lines[i++]);
+		g_input_stream_close (runner->priv->stdout, NULL, NULL);
+		g_object_unref (runner->priv->stdout);
+
+		runner->priv->stdout = NULL;
 	}
 
-	runner->priv->lines[0] = NULL;
+	gitg_io_close (GITG_IO (runner));
 }
 
 static void
-gitg_runner_finalize (GObject *object)
+gitg_runner_dispose (GObject *object)
 {
-	GitgRunner *runner = GITG_RUNNER (object);
-
-	/* Cancel possible running */
-	gitg_runner_cancel (runner);
+	GitgRunner *runner;
 
-	/* Free potential stored lines */
-	free_lines (runner);
+	runner = GITG_RUNNER (object);
 
-	/* Remove buffer slice */
-	g_slice_free1 (sizeof (gchar) * (runner->priv->buffer_size + 1), runner->priv->read_buffer);
-	g_slice_free1 (sizeof (gchar *) * (runner->priv->buffer_size + 1), runner->priv->lines);
+	if (runner->priv->command != NULL)
+	{
+		g_object_unref (runner->priv->command);
+		runner->priv->command = NULL;
+	}
 
-	/* Remove line buffer */
-	g_free (runner->priv->rest_buffer);
-	g_strfreev (runner->priv->environment);
+	gitg_io_cancel (GITG_IO (runner));
 
-	g_object_unref (runner->priv->cancellable);
+	close_streams (runner);
 
-	G_OBJECT_CLASS (gitg_runner_parent_class)->finalize (object);
+	G_OBJECT_CLASS (gitg_runner_parent_class)->dispose (object);
 }
 
 static void
-gitg_runner_get_property (GObject    *object,
-                          guint       prop_id,
-                          GValue     *value,
-                          GParamSpec *pspec)
+gitg_runner_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
 {
-	GitgRunner *runner = GITG_RUNNER (object);
+	GitgRunner *self = GITG_RUNNER (object);
 
 	switch (prop_id)
 	{
-		case PROP_BUFFER_SIZE:
-			g_value_set_uint (value, runner->priv->buffer_size);
-			break;
-		case PROP_SYNCHRONIZED:
-			g_value_set_boolean (value, runner->priv->synchronized);
-			break;
-		case PROP_PRESERVE_LINE_ENDINGS:
-			g_value_set_boolean (value, runner->priv->preserve_line_endings);
+		case PROP_COMMAND:
+			gitg_runner_set_command (self, g_value_get_object (value));
 			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-			break;
+		break;
 	}
 }
 
 static void
-set_buffer_size (GitgRunner *runner,
-                 guint       buffer_size)
-{
-	runner->priv->buffer_size = buffer_size;
-	runner->priv->lines = g_slice_alloc (sizeof (gchar *) * (runner->priv->buffer_size + 1));
-	runner->priv->lines[0] = NULL;
-
-	runner->priv->read_buffer = g_slice_alloc (sizeof (gchar) * (runner->priv->buffer_size + 1));
-}
-
-static void
-gitg_runner_set_property (GObject      *object,
-                          guint         prop_id,
-                          const GValue *value,
-                          GParamSpec   *pspec)
+gitg_runner_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
 {
-	GitgRunner *runner = GITG_RUNNER (object);
+	GitgRunner *self = GITG_RUNNER (object);
 
 	switch (prop_id)
 	{
-		case PROP_BUFFER_SIZE:
-			set_buffer_size (runner, g_value_get_uint (value));
-			break;
-		case PROP_SYNCHRONIZED:
-			runner->priv->synchronized = g_value_get_boolean (value);
-			break;
-		case PROP_PRESERVE_LINE_ENDINGS:
-			runner->priv->preserve_line_endings = g_value_get_boolean (value);
+		case PROP_COMMAND:
+			g_value_set_object (value, self->priv->command);
 			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-			break;
+		break;
 	}
 }
 
 static void
-gitg_runner_class_init (GitgRunnerClass *klass)
+dummy_cb (GPid     pid,
+          gint     status,
+          gpointer data)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->finalize = gitg_runner_finalize;
-
-	object_class->get_property = gitg_runner_get_property;
-	object_class->set_property = gitg_runner_set_property;
-
-	g_object_class_install_property (object_class, PROP_BUFFER_SIZE,
-	                                 g_param_spec_uint ("buffer_size",
-	                                                    "BUFFER SIZE",
-	                                                    "The runners buffer size",
-	                                                    1,
-	                                                    G_MAXUINT,
-	                                                    1,
-	                                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-	g_object_class_install_property (object_class, PROP_SYNCHRONIZED,
-	                                 g_param_spec_boolean ("synchronized",
-	                                                       "SYNCHRONIZED",
-	                                                       "Whether the command is ran synchronized",
-	                                                       FALSE,
-	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-	runner_signals[BEGIN_LOADING] =
-		g_signal_new ("begin-loading",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_LAST,
-		              G_STRUCT_OFFSET (GitgRunnerClass,
-		              begin_loading),
-		              NULL,
-		              NULL,
-		              g_cclosure_marshal_VOID__VOID,
-		              G_TYPE_NONE,
-		              0);
-
-	runner_signals[UPDATE] =
-		g_signal_new ("update",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_LAST,
-		              G_STRUCT_OFFSET (GitgRunnerClass,
-		              update),
-		              NULL,
-		              NULL,
-		              g_cclosure_marshal_VOID__POINTER,
-		              G_TYPE_NONE,
-		              1,
-		              G_TYPE_POINTER);
-
-	runner_signals[END_LOADING] =
-		g_signal_new ("end-loading",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_LAST,
-		              G_STRUCT_OFFSET (GitgRunnerClass,
-		              end_loading),
-		              NULL,
-		              NULL,
-		              g_cclosure_marshal_VOID__BOOLEAN,
-		              G_TYPE_NONE,
-		              1,
-		              G_TYPE_BOOLEAN);
-
-	g_type_class_add_private (object_class, sizeof (GitgRunnerPrivate));
-
-	g_object_class_install_property (object_class,
-	                                 PROP_PRESERVE_LINE_ENDINGS,
-	                                 g_param_spec_boolean ("preserve-line-endings",
-	                                                       "Preserve Line Endings",
-	                                                       "preserve line endings",
-	                                                       FALSE,
-	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 }
 
 static void
-gitg_runner_init (GitgRunner *self)
-{
-	self->priv = GITG_RUNNER_GET_PRIVATE (self);
-
-	self->priv->cancellable = g_cancellable_new ();
-}
-
-GitgRunner *
-gitg_runner_new (guint buffer_size)
-{
-	g_assert (buffer_size > 0);
-
-	return GITG_RUNNER (g_object_new (GITG_TYPE_RUNNER,
-	                                  "buffer_size",
-	                                  buffer_size,
-	                                  "synchronized",
-	                                  FALSE,
-	                                  NULL));
-}
-
-GitgRunner *
-gitg_runner_new_synchronized (guint buffer_size)
+kill_process (GitgRunner *runner)
 {
-	g_assert (buffer_size > 0);
-
-	return GITG_RUNNER (g_object_new (GITG_TYPE_RUNNER,
-	                                  "buffer_size",
-	                                  buffer_size,
-	                                  "synchronized",
-	                                  TRUE,
-	                                  NULL));
-}
-
-void
-gitg_runner_set_preserve_line_endings (GitgRunner *runner,
-                                       gboolean    preserve_line_endings)
-{
-	g_return_if_fail (GITG_IS_RUNNER (runner));
-
-	runner->priv->preserve_line_endings = preserve_line_endings;
-	g_object_notify (G_OBJECT (runner), "preserve-line-endings");
-}
-
-gboolean
-gitg_runner_get_preserve_line_endings (GitgRunner *runner)
-{
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
-
-	return runner->priv->preserve_line_endings;
-}
-
-static gchar *
-find_newline (gchar  *ptr,
-              gchar  *end,
-              gchar **line_end)
-{
-
-	while (ptr < end)
+	if (runner->priv->pid == 0)
 	{
-		gunichar c;
+		return;
+	}
 
-		c = g_utf8_get_char (ptr);
+	/* We remove our handler for the process here and install a dummy
+	   handler later so it will still be properly reaped */
+	g_source_remove (runner->priv->watch_id);
+	kill (runner->priv->pid, SIGTERM);
 
-		if (c == '\n')
-		{
-			/* That's it */
-			*line_end = g_utf8_next_char (ptr);
-			return ptr;
-		}
-		else if (c == '\r')
-		{
-			gchar *next;
-
-			next = g_utf8_next_char (ptr);
-
-			if (next < end)
-			{
-				gunichar n = g_utf8_get_char (next);
-
-				if (n == '\n')
-				{
-					/* Consume both! */
-					*line_end = g_utf8_next_char (next);
-					return ptr;
-				}
-				else
-				{
-					/* That's it! */
-					*line_end = next;
-					return ptr;
-				}
-			}
-			else
-			{
-				/* Need to save it, it might come later... */
-				break;
-			}
-		}
+	g_child_watch_add (runner->priv->pid, dummy_cb, NULL);
 
-		ptr = g_utf8_next_char (ptr);
-	}
+	runner->priv->pid = 0;
 
-	return NULL;
+	gitg_io_set_exit_status (GITG_IO (runner), EXIT_FAILURE);
 }
 
 static void
-parse_lines (GitgRunner *runner,
-             gchar      *buffer,
-             gssize      size)
+runner_done (GitgRunner *runner,
+             GError     *error)
 {
-	gchar *ptr;
-	gchar *newline = NULL;
-	gint i = 0;
-	gchar *all;
-	gchar *end;
-
-	free_lines (runner);
+	close_streams (runner);
+	kill_process (runner);
 
-	if (runner->priv->rest_buffer_size > 0)
+	if (!error && gitg_io_get_exit_status (GITG_IO (runner)) != 0)
 	{
-		GString *str = g_string_sized_new (runner->priv->rest_buffer_size + size);
-
-		g_string_append_len (str, runner->priv->rest_buffer, runner->priv->rest_buffer_size);
-		g_string_append_len (str, buffer, size);
+		GError *err;
 
-		all = g_string_free (str, FALSE);
-		size += runner->priv->rest_buffer_size;
+		err = g_error_new (G_IO_ERROR,
+		                   G_IO_ERROR_FAILED,
+		                   "Process exited with non-zero exit code: %d",
+		                   gitg_io_get_exit_status (GITG_IO (runner)));
 
-		g_free (runner->priv->rest_buffer);
-		runner->priv->rest_buffer = NULL;
-		runner->priv->rest_buffer_size = 0;
+		gitg_io_end (GITG_IO (runner), err);
+		g_error_free (err);
 	}
 	else
 	{
-		all = buffer;
+		gitg_io_end (GITG_IO (runner), error);
 	}
+}
 
-	ptr = all;
+static void
+gitg_runner_cancel (GitgIO *io)
+{
+	gboolean was_running;
+	GitgRunner *runner;
 
-	gchar *line_end;
-	end = ptr + size;
+	runner = GITG_RUNNER (io);
 
-	while ((newline = find_newline (ptr, end, &line_end)))
+	if (runner->priv->cancellable)
 	{
-		if (runner->priv->preserve_line_endings)
-		{
-			runner->priv->lines[i++] = g_strndup (ptr, line_end - ptr);
-		}
-		else
-		{
-			runner->priv->lines[i++] = g_strndup (ptr, newline - ptr);
-		}
-
-		ptr = line_end;
-	}
+		g_cancellable_cancel (runner->priv->cancellable);
 
-	if (ptr < end)
-	{
-		runner->priv->rest_buffer_size = end - ptr;
-		runner->priv->rest_buffer = g_strndup (ptr, runner->priv->rest_buffer_size);
+		g_object_unref (runner->priv->cancellable);
+		runner->priv->cancellable = NULL;
 	}
 
-	runner->priv->lines[i] = NULL;
+	was_running = gitg_io_get_running (GITG_IO (runner));
 
-	g_signal_emit (runner, runner_signals[UPDATE], 0, runner->priv->lines);
+	GITG_IO_CLASS (gitg_runner_parent_class)->cancel (GITG_IO (runner));
 
-	if (all != buffer)
+	if (was_running)
 	{
-		g_free (all);
+		runner_done (runner, NULL);
 	}
 }
 
 static void
-close_streams (GitgRunner *runner)
+gitg_runner_class_init (GitgRunnerClass *klass)
 {
-	if (runner->priv->output_stream)
-	{
-		g_output_stream_close (runner->priv->output_stream, NULL, NULL);
-		g_object_unref (runner->priv->output_stream);
-		runner->priv->output_stream = NULL;
-	}
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GitgIOClass *io_class = GITG_IO_CLASS (klass);
 
-	if (runner->priv->input_stream)
-	{
-		g_input_stream_close (runner->priv->input_stream, NULL, NULL);
-		g_object_unref (runner->priv->input_stream);
-		runner->priv->input_stream = NULL;
-	}
+	object_class->finalize = gitg_runner_finalize;
+	object_class->dispose = gitg_runner_dispose;
 
-	g_free (runner->priv->rest_buffer);
-	runner->priv->rest_buffer = NULL;
-	runner->priv->rest_buffer_size = 0;
-}
+	object_class->get_property = gitg_runner_get_property;
+	object_class->set_property = gitg_runner_set_property;
 
-static void
-emit_rest (GitgRunner *runner)
-{
-	if (runner->priv->rest_buffer_size > 0)
-	{
-		if (!runner->priv->preserve_line_endings &&
-		     runner->priv->rest_buffer[runner->priv->rest_buffer_size - 1] == '\r')
-		{
-			runner->priv->rest_buffer[runner->priv->rest_buffer_size - 1] = '\0';
-		}
+	io_class->cancel = gitg_runner_cancel;
 
-		gchar *b[] = {runner->priv->rest_buffer, NULL};
+	g_type_class_add_private (object_class, sizeof(GitgRunnerPrivate));
 
-		g_signal_emit (runner, runner_signals[UPDATE], 0, b);
-	}
+	g_object_class_install_property (object_class,
+	                                 PROP_COMMAND,
+	                                 g_param_spec_object ("command",
+	                                                      "Command",
+	                                                      "Command",
+	                                                      GITG_TYPE_COMMAND,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 }
 
-static gboolean
-run_sync (GitgRunner   *runner,
-          gchar const  *input,
-          GError      **error)
+static void
+gitg_runner_init (GitgRunner *self)
 {
-	if (input)
-	{
-		if (!g_output_stream_write_all (runner->priv->output_stream,
-		                                input,
-		                                strlen (input),
-		                                NULL,
-		                                NULL,
-		                                error))
-		{
-			runner_io_exit (runner->priv->pid, 1, runner);
-			close_streams (runner);
-
-			g_signal_emit (runner, runner_signals[END_LOADING], 0, FALSE);
-			return FALSE;
-		}
-
-		g_output_stream_close (runner->priv->output_stream, NULL, NULL);
-	}
-
-	gsize read = runner->priv->buffer_size;
-
-	while (read == runner->priv->buffer_size)
-	{
-		if (!g_input_stream_read_all (runner->priv->input_stream,
-		                              runner->priv->read_buffer,
-		                              runner->priv->buffer_size,
-		                              &read,
-		                              NULL,
-		                              error))
-		{
-			runner_io_exit (runner->priv->pid, 1, runner);
-			close_streams (runner);
-
-			g_signal_emit (runner, runner_signals[END_LOADING], 0, TRUE);
-			return FALSE;
-		}
-
-		runner->priv->read_buffer[read] = '\0';
-		parse_lines (runner, runner->priv->read_buffer, read);
-	}
-
-	emit_rest (runner);
-
-	gint status = 0;
-	waitpid (runner->priv->pid, &status, 0);
-
-	runner_io_exit (runner->priv->pid, status, runner);
-	close_streams (runner);
-
-	g_signal_emit (runner, runner_signals[END_LOADING], 0, FALSE);
-
-	if (status != 0 && error)
-	{
-		g_set_error (error,
-		             GITG_RUNNER_ERROR,
-		             GITG_RUNNER_ERROR_EXIT,
-		             "Did not exit without error code");
-	}
-
-	return status == EXIT_SUCCESS;
+	self->priv = GITG_RUNNER_GET_PRIVATE (self);
 }
 
-static void
-async_failed (AsyncData *data)
+GitgRunner *
+gitg_runner_new (GitgCommand *command)
 {
-	runner_io_exit (data->runner->priv->pid, 1, data->runner);
-	close_streams (data->runner);
-
-	g_signal_emit (data->runner, runner_signals[END_LOADING], 0, TRUE);
-
-	async_data_free (data);
+	return g_object_new (GITG_TYPE_RUNNER,
+	                     "command", command,
+	                     NULL);
 }
 
-static void start_reading (GitgRunner *runner, AsyncData *data);
-
 static void
-read_output_ready (GInputStream *stream,
-                   GAsyncResult *result,
-                   AsyncData    *data)
+splice_input_ready_cb (GOutputStream *source,
+                       GAsyncResult  *result,
+                       AsyncData     *data)
 {
 	GError *error = NULL;
+	gboolean ret;
 
-	gssize read = g_input_stream_read_finish (stream, result, &error);
+	ret = g_output_stream_splice_finish (source, result, &error);
 
 	if (g_cancellable_is_cancelled (data->cancellable))
 	{
-		g_input_stream_close (stream, NULL, NULL);
-		async_data_free (data);
-
 		if (error)
 		{
 			g_error_free (error);
 		}
 
+		async_data_free (data);
 		return;
 	}
 
-	if (read == -1)
+	if (!ret)
 	{
-		g_input_stream_close (stream, NULL, NULL);
-		async_failed (data);
-
-		if (error)
-		{
-			g_error_free (error);
-		}
-
-		return;
+		runner_done (data->runner, error);
 	}
 
-	if (read == 0)
-	{
-		/* End */
-		emit_rest (data->runner);
-
-		gint status = 0;
-		waitpid (data->runner->priv->pid, &status, 0);
-
-		runner_io_exit (data->runner->priv->pid, status, data->runner);
-		close_streams (data->runner);
-
-		g_signal_emit (data->runner,
-		               runner_signals[END_LOADING],
-		               0,
-		               FALSE);
-
-		async_data_free (data);
-	}
-	else
+	if (error)
 	{
-		data->runner->priv->read_buffer[read] = '\0';
-		parse_lines (data->runner,
-		             data->runner->priv->read_buffer,
-		             read);
-
-		if (g_cancellable_is_cancelled (data->cancellable))
-		{
-			g_input_stream_close (stream, NULL, NULL);
-			async_data_free (data);
-			return;
-		}
-
-		start_reading (data->runner, data);
+		g_error_free (error);
 	}
-}
 
-static void
-start_reading (GitgRunner *runner,
-               AsyncData  *data)
-{
-	g_input_stream_read_async (runner->priv->input_stream,
-	                           runner->priv->read_buffer,
-	                           runner->priv->buffer_size,
-	                           G_PRIORITY_DEFAULT,
-	                           runner->priv->cancellable,
-	                           (GAsyncReadyCallback)read_output_ready,
-	                           data);
+	async_data_free (data);
 }
 
 static void
-write_input_ready (GOutputStream *stream, GAsyncResult *result, AsyncData *data)
+splice_output_ready_cb (GOutputStream *source,
+                        GAsyncResult  *result,
+                        AsyncData     *data)
 {
 	GError *error = NULL;
-	g_output_stream_write_finish (stream, result, &error);
+	gboolean ret;
+
+	ret = g_output_stream_splice_finish (source, result, &error);
 
 	if (g_cancellable_is_cancelled (data->cancellable))
 	{
@@ -695,276 +322,217 @@ write_input_ready (GOutputStream *stream, GAsyncResult *result, AsyncData *data)
 		}
 
 		async_data_free (data);
+		return;
 	}
 
-	if (error)
+	if (!ret)
 	{
-		async_failed (data);
-		g_error_free (error);
+		runner_done (data->runner, error);
 	}
-	else
+	else if (data->runner->priv->pid == 0)
 	{
-		start_reading (data->runner, data);
+		runner_done (data->runner, NULL);
 	}
-}
-
-static gboolean
-gitg_runner_run_streams (GitgRunner     *runner,
-                         GInputStream   *input_stream,
-                         GOutputStream  *output_stream,
-                         gchar const    *input,
-                         GError        **error)
-{
-	gitg_runner_cancel (runner);
 
-	if (output_stream)
+	if (error)
 	{
-		runner->priv->output_stream = g_object_ref (output_stream);
+		g_error_free (error);
 	}
 
-	if (input_stream)
-	{
-		GitgSmartCharsetConverter *smart;
-
-		smart = gitg_smart_charset_converter_new (gitg_encoding_get_candidates ());
-
-		runner->priv->input_stream = g_converter_input_stream_new (input_stream,
-		                                                           G_CONVERTER (smart));
-
-		g_object_unref (smart);
-	}
+	async_data_free (data);
+}
 
-	/* Emit begin-loading signal */
-	g_signal_emit (runner, runner_signals[BEGIN_LOADING], 0);
+void
+gitg_runner_stream_close (GitgRunner *runner,
+                          GError     *error)
+{
+	g_return_if_fail (GITG_IS_RUNNER (runner));
 
-	if (runner->priv->synchronized)
+	if (runner->priv->pid == 0 || error)
 	{
-		return run_sync (runner, input, error);
+		runner_done (runner, error);
 	}
 	else
 	{
-		AsyncData *data = async_data_new (runner,
-		                                  runner->priv->cancellable);
-
-		if (input)
-		{
-			g_output_stream_write_async (runner->priv->output_stream,
-			                             input,
-			                             -1,
-			                             G_PRIORITY_DEFAULT,
-			                             runner->priv->cancellable,
-			                             (GAsyncReadyCallback)write_input_ready,
-			                             data);
-		}
-		else
-		{
-			start_reading (runner, data);
-		}
+		g_input_stream_close (runner->priv->stdout, NULL, NULL);
 	}
-	return TRUE;
 }
 
-gboolean
-gitg_runner_run_with_arguments (GitgRunner   *runner,
-                                GFile        *work_tree,
-                                gchar const **argv,
-                                gchar const  *input,
-                                GError      **error)
+static void
+process_watch_cb (GPid        pid,
+                  gint        status,
+                  GitgRunner *runner)
 {
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
-
-	gint stdoutf;
-	gint stdinf;
+	runner->priv->pid = 0;
 
-	gitg_runner_cancel (runner);
-	gchar *wd = NULL;
-
-	if (work_tree)
+	if (WIFEXITED (status))
 	{
-		wd = g_file_get_path (work_tree);
+		gitg_io_set_exit_status (GITG_IO (runner), WEXITSTATUS (status));
+	}
+	else
+	{
+		gitg_io_set_exit_status (GITG_IO (runner), 0);
 	}
 
-	gboolean ret = g_spawn_async_with_pipes (wd,
-	                                         (gchar **)argv,
-	                                         runner->priv->environment,
-	                                         G_SPAWN_SEARCH_PATH |
-	                                         G_SPAWN_DO_NOT_REAP_CHILD |
-	                                         (input ? 0 : G_SPAWN_CHILD_INHERITS_STDIN) |
-	                                         (gitg_debug_enabled (GITG_DEBUG_RUNNER) ? 0 : G_SPAWN_STDERR_TO_DEV_NULL),
-	                                         NULL,
-	                                         NULL,
-	                                         &(runner->priv->pid),
-	                                         input ? &stdinf : NULL,
-	                                         &stdoutf,
-	                                         NULL,
-	                                         error);
-
-	g_free (wd);
-
-	if (!ret)
+	/* Note that we don't emit 'done' here because the streams might not
+	   yet be ready with all their writing/reading */
+	if (runner->priv->cancellable)
 	{
-		runner->priv->pid = 0;
-		return FALSE;
+		g_object_unref (runner->priv->cancellable);
+		runner->priv->cancellable = NULL;
 	}
 
-	GOutputStream *output_stream = NULL;
-	GInputStream *input_stream;
+	runner->priv->watch_id = 0;
 
-	if (input)
+	if (runner->priv->stdout == NULL || g_input_stream_is_closed (runner->priv->stdout))
 	{
-		output_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf,
-		                                 TRUE));
+		runner_done (runner, NULL);
 	}
+}
 
-	input_stream = G_INPUT_STREAM (g_unix_input_stream_new (stdoutf, TRUE));
+void
+gitg_runner_run (GitgRunner *runner)
+{
+	gboolean ret;
+	gint stdinf;
+	gint stdoutf;
+	GFile *working_directory;
+	gchar *wd_path = NULL;
+	GInputStream *start_input;
+	GOutputStream *end_output;
+	GInputStream *output;
+	GitgSmartCharsetConverter *smart;
+	GError *error = NULL;
+
+	g_return_if_fail (GITG_IS_RUNNER (runner));
 
-	ret = gitg_runner_run_streams (runner,
-	                               input_stream,
-	                               output_stream,
-	                               input,
-	                               error);
+	gitg_io_cancel (GITG_IO (runner));
 
-	if (output_stream)
+	runner->priv->cancelled = FALSE;
+
+	working_directory = gitg_command_get_working_directory (runner->priv->command);
+
+	if (working_directory)
 	{
-		g_object_unref (output_stream);
+		wd_path = g_file_get_path (working_directory);
+		g_object_unref (working_directory);
 	}
 
-	g_object_unref (input_stream);
+	start_input = gitg_io_get_input (GITG_IO (runner));
 
-	return ret;
-}
+	ret = g_spawn_async_with_pipes (wd_path,
+	                                (gchar **)gitg_command_get_arguments (runner->priv->command),
+	                                (gchar **)gitg_command_get_environment (runner->priv->command),
+	                                G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
+	                                (gitg_debug_enabled (GITG_DEBUG_RUNNER) ? 0 : G_SPAWN_STDERR_TO_DEV_NULL),
+	                                NULL,
+	                                NULL,
+	                                &(runner->priv->pid),
+	                                start_input ? &stdinf : NULL,
+	                                &stdoutf,
+	                                NULL,
+	                                &error);
 
-gboolean
-gitg_runner_run (GitgRunner   *runner,
-                 gchar const **argv,
-                 GError      **error)
-{
-	return gitg_runner_run_with_arguments (runner, NULL, argv, NULL, error);
-}
+	g_free (wd_path);
 
-gboolean
-gitg_runner_run_stream (GitgRunner    *runner,
-                        GInputStream  *stream,
-                        GError       **error)
-{
-	return gitg_runner_run_streams (runner, stream, NULL, NULL, error);
-}
-
-guint
-gitg_runner_get_buffer_size (GitgRunner *runner)
-{
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), 0);
-	return runner->priv->buffer_size;
-}
+	gitg_io_begin (GITG_IO (runner));
 
-static void
-dummy_cb (GPid     pid,
-          gint     status,
-          gpointer data)
-{
-}
+	if (!ret)
+	{
+		runner_done (runner, error);
+		g_error_free (error);
+		return;
+	}
 
-void
-gitg_runner_cancel (GitgRunner *runner)
-{
-	g_return_if_fail (GITG_IS_RUNNER (runner));
+	runner->priv->watch_id = g_child_watch_add (runner->priv->pid,
+	                                            (GChildWatchFunc)process_watch_cb,
+	                                            runner);
 
-	if (runner->priv->input_stream)
+	if (start_input)
 	{
-		g_cancellable_cancel (runner->priv->cancellable);
-		g_object_unref (runner->priv->cancellable);
+		AsyncData *data;
 
 		runner->priv->cancellable = g_cancellable_new ();
 
-		if (runner->priv->pid)
-		{
-			g_child_watch_add (runner->priv->pid, dummy_cb, NULL);
-			kill (runner->priv->pid, SIGTERM);
+		runner->priv->stdin = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf,
+		                                              TRUE));
 
-			runner_io_exit (runner->priv->pid, EXIT_FAILURE, runner);
-		}
+		data = async_data_new (runner);
 
-		close_streams (runner);
-		g_signal_emit (runner, runner_signals[END_LOADING], 0, TRUE);
+		/* Splice the supplied input to stdin of the process */
+		g_output_stream_splice_async (runner->priv->stdin,
+		                              start_input,
+		                              G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+		                              G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+		                              G_PRIORITY_DEFAULT,
+		                              runner->priv->cancellable,
+		                              (GAsyncReadyCallback)splice_input_ready_cb,
+		                              data);
 	}
-}
 
-gboolean
-gitg_runner_running (GitgRunner *runner)
-{
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
-	return runner->priv->input_stream != NULL;
-}
+	output = G_INPUT_STREAM (g_unix_input_stream_new (stdoutf,
+	                                                  TRUE));
 
-gint
-gitg_runner_get_exit_status (GitgRunner *runner)
-{
-	g_return_val_if_fail (GITG_IS_RUNNER (runner), 1);
+	smart = gitg_smart_charset_converter_new (gitg_encoding_get_candidates ());
 
-	return runner->priv->exit_status;
-}
+	runner->priv->stdout = g_converter_input_stream_new (output,
+	                                                     G_CONVERTER (smart));
 
-void
-gitg_runner_set_environment (GitgRunner   *runner,
-                             gchar const **environment)
-{
-	g_return_if_fail (GITG_IS_RUNNER (runner));
+	g_object_unref (smart);
+	g_object_unref (output);
 
-	g_strfreev (runner->priv->environment);
+	end_output = gitg_io_get_output (GITG_IO (runner));
 
-	if (environment == NULL)
-	{
-		runner->priv->environment = NULL;
-	}
-	else
+	if (end_output)
 	{
-		gint len = g_strv_length ((gchar **)environment);
+		AsyncData *data;
 
-		runner->priv->environment = g_new (gchar *, len + 1);
-		gint i;
-
-		for (i = 0; i < len; ++i)
+		if (runner->priv->cancellable == NULL)
 		{
-			runner->priv->environment[i] = g_strdup (environment[i]);
+			runner->priv->cancellable = g_cancellable_new ();
 		}
 
-		runner->priv->environment[len] = NULL;
+		data = async_data_new (runner);
+
+		/* Splice output of the process into the provided stream */
+		g_output_stream_splice_async (end_output,
+		                              runner->priv->stdout,
+		                              G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+		                              G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+		                              G_PRIORITY_DEFAULT,
+		                              runner->priv->cancellable,
+		                              (GAsyncReadyCallback)splice_output_ready_cb,
+		                              data);
 	}
 }
 
+GInputStream *
+gitg_runner_get_stream (GitgRunner *runner)
+{
+	g_return_val_if_fail (GITG_IS_RUNNER (runner), NULL);
+
+	return runner->priv->stdout;
+}
+
 void
-gitg_runner_add_environment (GitgRunner  *runner,
-                             gchar const *key,
-                             gchar const *value)
+gitg_runner_set_command (GitgRunner *runner, GitgCommand *command)
 {
 	g_return_if_fail (GITG_IS_RUNNER (runner));
-	g_return_if_fail (key != NULL);
-	g_return_if_fail (value != NULL);
+	g_return_if_fail (GITG_IS_COMMAND (command));
 
-	if (runner->priv->environment == NULL)
+	if (runner->priv->command)
 	{
-		gchar **all = g_listenv ();
-
-		gint i = 0;
-		runner->priv->environment = g_malloc (sizeof (gchar *) *
-		                                      (g_strv_length (all) + 1));
-
-		while (all && all[i])
-		{
-			runner->priv->environment[i] = g_strconcat (all[i],
-			                                            "=",
-			                                            g_getenv (all[i]),
-			                                            NULL);
-			++i;
-		}
-
-		runner->priv->environment[i] = NULL;
+		g_object_unref (runner->priv->command);
 	}
 
-	gint len = g_strv_length (runner->priv->environment);
-	runner->priv->environment = g_realloc (runner->priv->environment,
-	                                       sizeof (gchar *) * (len + 2));
+	runner->priv->command = g_object_ref_sink (command);
+	g_object_notify (G_OBJECT (runner), "command");
+}
+
+GitgCommand *
+gitg_runner_get_command (GitgRunner *runner)
+{
+	g_return_val_if_fail (GITG_IS_RUNNER (runner), NULL);
 
-	runner->priv->environment[len] = g_strconcat (key, "=", value, NULL);
-	runner->priv->environment[len + 1] = NULL;
+	return runner->priv->command;
 }
diff --git a/libgitg/gitg-runner.h b/libgitg/gitg-runner.h
index 589ffe1..8b02db4 100644
--- a/libgitg/gitg-runner.h
+++ b/libgitg/gitg-runner.h
@@ -1,102 +1,53 @@
-/*
- * gitg-runner.h
- * This file is part of gitg - git repository viewer
- *
- * Copyright (C) 2009 - Jesse van den Kieboom
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, 
- * Boston, MA 02111-1307, USA.
- */
-
 #ifndef __GITG_RUNNER_H__
 #define __GITG_RUNNER_H__
 
 #include <glib-object.h>
+#include <libgitg/gitg-command.h>
+#include <libgitg/gitg-io.h>
 #include <gio/gio.h>
 
 G_BEGIN_DECLS
 
-#define GITG_TYPE_RUNNER			(gitg_runner_get_type ())
-#define GITG_RUNNER(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_RUNNER, GitgRunner))
+#define GITG_TYPE_RUNNER		(gitg_runner_get_type ())
+#define GITG_RUNNER(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_RUNNER, GitgRunner))
 #define GITG_RUNNER_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_RUNNER, GitgRunner const))
 #define GITG_RUNNER_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_RUNNER, GitgRunnerClass))
-#define GITG_IS_RUNNER(obj)			(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_RUNNER))
+#define GITG_IS_RUNNER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_RUNNER))
 #define GITG_IS_RUNNER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_RUNNER))
 #define GITG_RUNNER_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_RUNNER, GitgRunnerClass))
 
-#define GITG_RUNNER_ERROR			(gitg_runner_error_quark())
-
-typedef struct _GitgRunner			GitgRunner;
+typedef struct _GitgRunner		GitgRunner;
 typedef struct _GitgRunnerClass		GitgRunnerClass;
 typedef struct _GitgRunnerPrivate	GitgRunnerPrivate;
 
-typedef enum
+struct _GitgRunner
 {
-	GITG_RUNNER_ERROR_NONE = 0,
-	GITG_RUNNER_ERROR_EXIT
-} GitgRunnerError;
-
-struct _GitgRunner {
-	GObject parent;
+	/*< private >*/
+	GitgIO parent;
 
 	GitgRunnerPrivate *priv;
+
+	/*< public >*/
 };
 
-struct _GitgRunnerClass {
-	GObjectClass parent_class;
+struct _GitgRunnerClass
+{
+	/*< private >*/
+	GitgIOClass parent_class;
 
-	/* signals */
-	void (* begin_loading) (GitgRunner *runner);
-	void (* update) (GitgRunner *runner, gchar **buffer);
-	void (* end_loading) (GitgRunner *runner, gboolean cancelled);
+	/*< public >*/
 };
 
 GType gitg_runner_get_type (void) G_GNUC_CONST;
-GitgRunner *gitg_runner_new (guint buffer_size);
-GitgRunner *gitg_runner_new_synchronized (guint buffer_size);
-
-guint gitg_runner_get_buffer_size (GitgRunner *runner);
-
-gboolean gitg_runner_run_stream (GitgRunner *runner,
-                                 GInputStream *stream,
-                                 GError **error);
-
-gboolean gitg_runner_run_with_arguments (GitgRunner *runner,
-                                         GFile *work_tree,
-                                         gchar const **argv,
-                                         gchar const *input,
-                                         GError **error);
-
-gboolean gitg_runner_run (GitgRunner *runner,
-                          gchar const **argv,
-                          GError **error);
-
-gboolean gitg_runner_running (GitgRunner *runner);
-
-gint gitg_runner_get_exit_status (GitgRunner *runner);
-void gitg_runner_cancel (GitgRunner *runner);
-
-void gitg_runner_set_environment (GitgRunner *runner, gchar const **environment);
-void gitg_runner_add_environment (GitgRunner *runner, gchar const *key, gchar const *value);
+GitgRunner *gitg_runner_new (GitgCommand *command);
 
-void gitg_runner_set_preserve_line_endings (GitgRunner *runner,
-                                            gboolean    preserve_line_endings);
+void gitg_runner_run (GitgRunner *runner);
 
-gboolean gitg_runner_get_preserve_line_endings (GitgRunner *runner);
+GitgCommand *gitg_runner_get_command (GitgRunner *runner);
+void gitg_runner_set_command (GitgRunner *runner, GitgCommand *command);
 
-GQuark gitg_runner_error_quark (void);
+GInputStream *gitg_runner_get_stream (GitgRunner *runner);
+void gitg_runner_stream_close (GitgRunner *runner, GError *error);
 
 G_END_DECLS
 
diff --git a/libgitg/gitg-shell.c b/libgitg/gitg-shell.c
new file mode 100644
index 0000000..9d8937b
--- /dev/null
+++ b/libgitg/gitg-shell.c
@@ -0,0 +1,1052 @@
+/*
+ * gitg-shell.c
+ * This file is part of gitg - git repository viewer
+ *
+ * Copyright (C) 2009 - Jesse van den Kieboom
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gitg-convert.h"
+#include "gitg-debug.h"
+#include "gitg-shell.h"
+#include "gitg-smart-charset-converter.h"
+#include "gitg-runner.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+
+#include "gitg-line-parser.h"
+
+#define GITG_SHELL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_SHELL, GitgShellPrivate))
+
+/* Signals */
+enum
+{
+	UPDATE,
+	LAST_SIGNAL
+};
+
+static guint shell_signals[LAST_SIGNAL] = { 0 };
+
+/* Properties */
+enum
+{
+	PROP_0,
+
+	PROP_BUFFER_SIZE,
+	PROP_SYNCHRONIZED,
+	PROP_PRESERVE_LINE_ENDINGS
+};
+
+struct _GitgShellPrivate
+{
+	GSList *runners;
+
+	GCancellable *cancellable;
+	GError *error;
+
+	GMainLoop *main_loop;
+	GitgRunner *last_runner;
+
+	guint buffer_size;
+	GitgLineParser *line_parser;
+
+	guint synchronized : 1;
+	guint preserve_line_endings : 1;
+	guint cancelled : 1;
+	guint read_done : 1;
+};
+
+static void shell_done (GitgShell *shell, GError *error);
+
+G_DEFINE_TYPE (GitgShell, gitg_shell, GITG_TYPE_IO)
+
+static void
+runner_end (GitgRunner *runner,
+            GError     *error,
+            GitgShell  *shell)
+{
+	if (!shell->priv->runners)
+	{
+		return;
+	}
+
+	if ((runner == shell->priv->last_runner && shell->priv->read_done) || error)
+	{
+		shell_done (shell, error);
+	}
+}
+
+static void
+close_runners (GitgShell *shell)
+{
+	GSList *item;
+
+	for (item = shell->priv->runners; item; item = g_slist_next (item))
+	{
+		GitgRunner *runner = item->data;
+
+		g_signal_handlers_disconnect_by_func (runner,
+		                                      runner_end,
+		                                      shell);
+
+		gitg_io_close (GITG_IO (runner));
+		g_object_unref (runner);
+	}
+
+	g_slist_free (shell->priv->runners);
+	shell->priv->runners = NULL;
+
+	if (shell->priv->line_parser)
+	{
+		g_object_unref (shell->priv->line_parser);
+		shell->priv->line_parser = NULL;
+	}
+
+	shell->priv->last_runner = NULL;
+}
+
+static void
+gitg_shell_finalize (GObject *object)
+{
+	GitgShell *shell = GITG_SHELL (object);
+
+	/* Cancel possible running */
+	gitg_io_cancel (GITG_IO (shell));
+
+	if (shell->priv->cancellable)
+	{
+		g_object_unref (shell->priv->cancellable);
+	}
+
+	G_OBJECT_CLASS (gitg_shell_parent_class)->finalize (object);
+}
+
+static void
+gitg_shell_dispose (GObject *object)
+{
+	GitgShell *shell;
+
+	shell = GITG_SHELL (object);
+
+	close_runners (shell);
+}
+
+static void
+gitg_shell_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+	GitgShell *shell = GITG_SHELL (object);
+
+	switch (prop_id)
+	{
+		case PROP_BUFFER_SIZE:
+			g_value_set_uint (value, shell->priv->buffer_size);
+			break;
+		case PROP_SYNCHRONIZED:
+			g_value_set_boolean (value, shell->priv->synchronized);
+			break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			g_value_set_boolean (value, shell->priv->preserve_line_endings);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+gitg_shell_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+	GitgShell *shell = GITG_SHELL (object);
+
+	switch (prop_id)
+	{
+		case PROP_BUFFER_SIZE:
+			shell->priv->buffer_size = g_value_get_uint (value);
+			break;
+		case PROP_SYNCHRONIZED:
+			shell->priv->synchronized = g_value_get_boolean (value);
+			break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			shell->priv->preserve_line_endings = g_value_get_boolean (value);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+gitg_shell_cancel (GitgIO *io)
+{
+	gboolean was_running;
+	GitgShell *shell;
+
+	shell = GITG_SHELL (io);
+
+	if (shell->priv->line_parser)
+	{
+		g_object_unref (shell->priv->line_parser);
+		shell->priv->line_parser = NULL;
+	}
+
+	was_running = gitg_io_get_running (io);
+
+	GITG_IO_CLASS (gitg_shell_parent_class)->cancel (io);
+
+	if (was_running)
+	{
+		shell_done (GITG_SHELL (io), NULL);
+	}
+}
+
+static void
+gitg_shell_class_init (GitgShellClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GitgIOClass *io_class = GITG_IO_CLASS (klass);
+
+	object_class->finalize = gitg_shell_finalize;
+	object_class->dispose = gitg_shell_dispose;
+
+	object_class->get_property = gitg_shell_get_property;
+	object_class->set_property = gitg_shell_set_property;
+
+	io_class->cancel = gitg_shell_cancel;
+
+	g_object_class_install_property (object_class, PROP_BUFFER_SIZE,
+	                                 g_param_spec_uint ("buffer_size",
+	                                                    "BUFFER SIZE",
+	                                                    "The shells buffer size",
+	                                                    1,
+	                                                    G_MAXUINT,
+	                                                    1,
+	                                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (object_class, PROP_SYNCHRONIZED,
+	                                 g_param_spec_boolean ("synchronized",
+	                                                       "SYNCHRONIZED",
+	                                                       "Whether the command is ran synchronized",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_PRESERVE_LINE_ENDINGS,
+	                                 g_param_spec_boolean ("preserve-line-endings",
+	                                                       "Preserve Line Endings",
+	                                                       "preserve line endings",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	shell_signals[UPDATE] =
+		g_signal_new ("update",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              G_STRUCT_OFFSET (GitgShellClass, update),
+		              NULL,
+		              NULL,
+		              g_cclosure_marshal_VOID__POINTER,
+		              G_TYPE_NONE,
+		              1,
+		              G_TYPE_POINTER);
+
+	g_type_class_add_private (object_class, sizeof (GitgShellPrivate));
+}
+
+static void
+gitg_shell_init (GitgShell *self)
+{
+	self->priv = GITG_SHELL_GET_PRIVATE (self);
+
+	self->priv->cancellable = g_cancellable_new ();
+}
+
+GitgShell *
+gitg_shell_new (guint buffer_size)
+{
+	g_assert (buffer_size > 0);
+
+	return GITG_SHELL (g_object_new (GITG_TYPE_SHELL,
+	                                  "buffer_size",
+	                                  buffer_size,
+	                                  "synchronized",
+	                                  FALSE,
+	                                  NULL));
+}
+
+GitgShell *
+gitg_shell_new_synchronized (guint buffer_size)
+{
+	g_assert (buffer_size > 0);
+
+	return GITG_SHELL (g_object_new (GITG_TYPE_SHELL,
+	                                  "buffer_size",
+	                                  buffer_size,
+	                                  "synchronized",
+	                                  TRUE,
+	                                  NULL));
+}
+
+void
+gitg_shell_set_preserve_line_endings (GitgShell *shell,
+                                       gboolean    preserve_line_endings)
+{
+	g_return_if_fail (GITG_IS_SHELL (shell));
+
+	shell->priv->preserve_line_endings = preserve_line_endings;
+	g_object_notify (G_OBJECT (shell), "preserve-line-endings");
+}
+
+gboolean
+gitg_shell_get_preserve_line_endings (GitgShell *shell)
+{
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+
+	return shell->priv->preserve_line_endings;
+}
+
+static void
+shell_done (GitgShell *shell,
+            GError    *error)
+{
+	if (shell->priv->error)
+	{
+		g_error_free (shell->priv->error);
+		shell->priv->error = NULL;
+	}
+
+	if (error)
+	{
+		shell->priv->error = g_error_copy (error);
+		gitg_io_set_exit_status (GITG_IO (shell), EXIT_FAILURE);
+	}
+
+	if (shell->priv->main_loop)
+	{
+		g_main_loop_quit (shell->priv->main_loop);
+		g_main_loop_unref (shell->priv->main_loop);
+
+		shell->priv->main_loop = NULL;
+	}
+
+	if (shell->priv->runners == NULL)
+	{
+		return;
+	}
+
+	if (shell->priv->cancellable)
+	{
+		g_cancellable_cancel (shell->priv->cancellable);
+		g_object_unref (shell->priv->cancellable);
+
+		shell->priv->cancellable = NULL;
+	}
+
+	/* Take over the exit code of the last runner */
+	if (!error)
+	{
+		gitg_io_set_exit_status (GITG_IO (shell),
+		                         gitg_io_get_exit_status (GITG_IO (shell->priv->last_runner)));
+	}
+
+	close_runners (shell);
+	gitg_io_close (GITG_IO (shell));
+
+	gitg_io_end (GITG_IO (shell), error);
+}
+
+static gboolean
+run_sync (GitgShell  *shell,
+          GError    **error)
+{
+	g_main_loop_run (shell->priv->main_loop);
+
+	if (shell->priv->error)
+	{
+		g_propagate_error (error, shell->priv->error);
+		shell->priv->error = NULL;
+
+		return FALSE;
+	}
+
+	return gitg_io_get_exit_status (GITG_IO (shell)) == 0;
+}
+
+static void
+on_lines_done_cb (GitgLineParser *parser,
+                  GError         *error,
+                  GitgShell      *shell)
+{
+	if (!shell->priv->read_done)
+	{
+		shell->priv->read_done = TRUE;
+
+		if (shell->priv->last_runner == NULL)
+		{
+			shell_done (shell, error);
+		}
+		else
+		{
+			gitg_runner_stream_close (shell->priv->last_runner, NULL);
+		}
+	}
+}
+
+static void
+on_lines_cb (GitgLineParser  *parser,
+             gchar          **lines,
+             GitgShell       *shell)
+{
+	g_signal_emit (shell, shell_signals[UPDATE], 0, lines);
+}
+
+static void
+run_stream (GitgShell    *shell,
+            GInputStream *stream)
+{
+	shell->priv->cancellable = g_cancellable_new ();
+
+	shell->priv->read_done = FALSE;
+
+	shell->priv->line_parser = gitg_line_parser_new (shell->priv->buffer_size,
+	                                                 shell->priv->preserve_line_endings);
+
+	g_signal_connect (shell->priv->line_parser,
+	                  "lines",
+	                  G_CALLBACK (on_lines_cb),
+	                  shell);
+
+	g_signal_connect (shell->priv->line_parser,
+	                  "done",
+	                  G_CALLBACK (on_lines_done_cb),
+	                  shell);
+
+	gitg_line_parser_parse (shell->priv->line_parser,
+	                        stream,
+	                        shell->priv->cancellable);
+}
+
+static gboolean
+run_commands (GitgShell    *shell,
+              GitgCommand **commands,
+              GError      **error)
+{
+	GitgIO *io;
+	GitgRunner *prev = NULL;
+	GOutputStream *output;
+	gboolean ret = TRUE;
+	GitgCommand **ptr;
+
+	io = GITG_IO (shell);
+	output = gitg_io_get_output (io);
+
+	shell->priv->read_done = TRUE;
+
+	gitg_io_cancel (GITG_IO (shell));
+
+	gitg_io_begin (GITG_IO (shell));
+
+	/* Ref sink all commands */
+	for (ptr = commands; *ptr; ++ptr)
+	{
+		g_object_ref_sink (*ptr);
+	}
+
+	if (shell->priv->synchronized)
+	{
+		shell->priv->main_loop = g_main_loop_new (NULL, FALSE);
+	}
+
+	/* Setup runners */
+	for (ptr = commands; *ptr; ++ptr)
+	{
+		GitgRunner *runner;
+
+		runner = gitg_runner_new (*ptr);
+
+		g_signal_connect (runner,
+		                  "end",
+		                  G_CALLBACK (runner_end),
+		                  shell);
+
+		if (ptr == commands)
+		{
+			/* Copy input set on the shell to the first runner */
+			GInputStream *input;
+
+			input = gitg_io_get_input (io);
+
+			if (input != NULL)
+			{
+				gitg_io_set_input (GITG_IO (runner), input);
+			}
+		}
+		else
+		{
+			/* Set output of the previous runner to the input of
+			   this runner */
+			gitg_io_set_input (GITG_IO (runner),
+			                   gitg_runner_get_stream (prev));
+		}
+
+		if (!*(ptr + 1))
+		{
+			shell->priv->last_runner = runner;
+
+			/* Copy output set on the shell to the last runner */
+			if (output != NULL)
+			{
+				gitg_io_set_output (GITG_IO (runner), output);
+			}
+		}
+
+		shell->priv->runners = g_slist_append (shell->priv->runners,
+		                                       runner);
+
+		/* Start the runner */
+		gitg_runner_run (runner);
+
+		if (shell->priv->runners == NULL)
+		{
+			/* This means it there was an error */
+			if (error && shell->priv->error)
+			{
+				*error = g_error_copy (shell->priv->error);
+			}
+
+			if (shell->priv->error)
+			{
+				g_error_free (shell->priv->error);
+				shell->priv->error = NULL;
+			}
+
+			ret = FALSE;
+			goto cleanup;
+		}
+
+		prev = runner;
+	}
+
+	/* Setup line reader if necessary in async mode */
+	if (output == NULL)
+	{
+		run_stream (shell, gitg_runner_get_stream (shell->priv->last_runner));
+	}
+
+	if (shell->priv->synchronized)
+	{
+		return run_sync (shell, error);
+	}
+
+cleanup:
+	for (ptr = commands; *ptr; ++ptr)
+	{
+		g_object_unref (*ptr);
+	}
+
+	if (shell->priv->main_loop)
+	{
+		g_main_loop_unref (shell->priv->main_loop);
+		shell->priv->main_loop = NULL;
+	}
+
+	return ret;
+}
+
+gboolean
+gitg_shell_run (GitgShell    *shell,
+                GitgCommand  *command,
+                GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+	g_return_val_if_fail (GITG_IS_COMMAND (command), FALSE);
+
+	return gitg_shell_runv (shell, error, command, NULL);
+}
+
+gboolean
+gitg_shell_run_list (GitgShell    *shell,
+                     GitgCommand **commands,
+                     GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+
+	return run_commands (shell, commands, error);
+}
+
+gboolean
+gitg_shell_runva (GitgShell  *shell,
+                  va_list     ap,
+                  GError    **error)
+{
+	GPtrArray *ptr;
+	GitgCommand **commands;
+	GitgCommand *command;
+	gboolean ret;
+	guint num = 0;
+
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+
+	ptr = g_ptr_array_new ();
+
+	while ((command = va_arg (ap, GitgCommand *)) != NULL)
+	{
+		g_ptr_array_add (ptr, command);
+		++num;
+	}
+
+	if (num == 0)
+	{
+		g_ptr_array_free (ptr, TRUE);
+		return FALSE;
+	}
+
+	g_ptr_array_add (ptr, NULL);
+
+	commands = (GitgCommand **)g_ptr_array_free (ptr, FALSE);
+
+	ret = gitg_shell_run_list (shell, commands, error);
+
+	g_free (commands);
+
+	return ret;
+}
+
+gboolean
+gitg_shell_runv (GitgShell  *shell,
+                 GError    **error,
+                 ...)
+{
+	va_list ap;
+	gboolean ret;
+
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+
+	va_start (ap, error);
+	ret = gitg_shell_runva (shell, ap, error);
+	va_end (ap);
+
+	return ret;
+}
+
+guint
+gitg_shell_get_buffer_size (GitgShell *shell)
+{
+	g_return_val_if_fail (GITG_IS_SHELL (shell), 0);
+	return shell->priv->buffer_size;
+}
+
+gchar **
+gitg_shell_run_sync_with_output (GitgCommand  *command,
+                                 gboolean      preserve_line_endings,
+                                 GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+
+	return gitg_shell_run_sync_with_outputv (preserve_line_endings,
+	                                         error,
+	                                         command,
+	                                         NULL);
+}
+
+static void
+collect_update (GitgShell           *shell,
+                gchar const * const *lines,
+                GPtrArray           *ret)
+{
+	while (lines && *lines)
+	{
+		g_ptr_array_add (ret, g_strdup (*lines++));
+	}
+}
+
+gchar **
+gitg_shell_run_sync_with_input_and_output_list (GitgCommand **commands,
+                                                gboolean      preserve_line_endings,
+                                                const gchar  *input,
+                                                GError      **error)
+{
+	GitgShell *shell;
+	GPtrArray *ret;
+	gboolean res;
+	gchar **val;
+
+	shell = gitg_shell_new_synchronized (1000);
+
+	gitg_shell_set_preserve_line_endings (shell, preserve_line_endings);
+
+	ret = g_ptr_array_sized_new (100);
+
+	g_signal_connect (shell,
+	                  "update",
+	                  G_CALLBACK (collect_update),
+	                  ret);
+
+	if (input)
+	{
+		GInputStream *stream;
+
+		stream = g_memory_input_stream_new_from_data (g_strdup (input),
+		                                              -1,
+		                                              (GDestroyNotify)g_free);
+
+		gitg_io_set_input (GITG_IO (shell), stream);
+		g_object_unref (stream);
+	}
+
+	res = gitg_shell_run_list (shell, commands, error);
+
+	g_ptr_array_add (ret, NULL);
+
+	if (!res || gitg_io_get_exit_status (GITG_IO (shell)) != 0)
+	{
+		g_strfreev ((gchar **)g_ptr_array_free (ret, FALSE));
+		g_object_unref (shell);
+
+		return NULL;
+	}
+
+	val = (gchar **)g_ptr_array_free (ret, FALSE);
+	g_object_unref (shell);
+
+	return val;
+
+}
+
+static gchar **
+gitg_shell_run_sync_with_input_and_outputva (gboolean      preserve_line_endings,
+                                             const gchar  *input,
+                                             va_list       ap,
+                                             GError      **error)
+{
+	GPtrArray *commands;
+	GitgCommand *cmd;
+	GitgCommand **cmds;
+	gchar **ret;
+
+	commands = g_ptr_array_new ();
+
+	while ((cmd = va_arg (ap, GitgCommand *)))
+	{
+		g_ptr_array_add (commands, cmd);
+	}
+
+	g_ptr_array_add (commands, NULL);
+	cmds = (GitgCommand **)g_ptr_array_free (commands, FALSE);
+
+	ret = gitg_shell_run_sync_with_input_and_output_list (cmds,
+	                                                      preserve_line_endings,
+	                                                      input,
+	                                                      error);
+
+	g_free (cmds);
+	return ret;
+}
+
+static gchar **
+gitg_shell_run_sync_with_outputva (gboolean   preserve_line_endings,
+                                   va_list    ap,
+                                   GError   **error)
+{
+	return gitg_shell_run_sync_with_input_and_outputva (preserve_line_endings,
+	                                                    NULL,
+	                                                    ap,
+	                                                    error);
+}
+
+gchar **
+gitg_shell_run_sync_with_output_list (GitgCommand **commands,
+                                      gboolean      preserve_line_endings,
+                                      GError      **error)
+{
+	return gitg_shell_run_sync_with_input_and_output_list (commands,
+	                                                       preserve_line_endings,
+	                                                       NULL,
+	                                                       error);
+}
+
+gchar **
+gitg_shell_run_sync_with_outputv (gboolean   preserve_line_endings,
+                                  GError   **error,
+                                  ...)
+{
+	va_list ap;
+	gchar **ret;
+
+	va_start (ap, error);
+	ret = gitg_shell_run_sync_with_outputva (preserve_line_endings,
+	                                         ap,
+	                                         error);
+	va_end (ap);
+
+	return ret;
+}
+
+gboolean
+gitg_shell_run_sync (GitgCommand  *command,
+                     GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), FALSE);
+
+	return gitg_shell_run_syncv (error, command, NULL);
+}
+
+gboolean
+gitg_shell_run_sync_list (GitgCommand **commands,
+                          GError      **error)
+{
+	gchar **res;
+
+	res = gitg_shell_run_sync_with_output_list (commands, FALSE, error);
+
+	if (res)
+	{
+		g_strfreev (res);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+gboolean
+gitg_shell_run_syncv (GError **error,
+                      ...)
+{
+	va_list ap;
+	gchar **res;
+
+	va_start (ap, error);
+	res = gitg_shell_run_sync_with_outputva (FALSE, ap, error);
+	va_end (ap);
+
+	if (res)
+	{
+		g_strfreev (res);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+gboolean
+gitg_shell_run_sync_with_input (GitgCommand  *command,
+                                const gchar  *input,
+                                GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), FALSE);
+
+	return gitg_shell_run_sync_with_inputv (input, error, command, NULL);
+}
+
+gboolean
+gitg_shell_run_sync_with_input_list (GitgCommand  **commands,
+                                     const gchar  *input,
+                                     GError      **error)
+{
+	gchar **ret;
+
+	ret = gitg_shell_run_sync_with_input_and_output_list (commands,
+	                                                      FALSE,
+	                                                      input,
+	                                                      error);
+
+	if (ret)
+	{
+		g_strfreev (ret);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+gboolean
+gitg_shell_run_sync_with_inputv (const gchar  *input,
+                                 GError      **error,
+                                 ...)
+{
+	va_list ap;
+	gchar **ret;
+
+	va_start (ap, error);
+	ret = gitg_shell_run_sync_with_input_and_outputva (FALSE,
+	                                                   input,
+	                                                   ap,
+	                                                   error);
+	va_end (ap);
+
+	if (ret)
+	{
+		g_strfreev (ret);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+gchar **
+gitg_shell_run_sync_with_input_and_output (GitgCommand  *command,
+                                           gboolean      preserve_line_endings,
+                                           const gchar  *input,
+                                           GError      **error)
+{
+	g_return_val_if_fail (GITG_IS_COMMAND (command), NULL);
+
+	return gitg_shell_run_sync_with_input_and_outputv (preserve_line_endings,
+	                                                   input,
+	                                                   error,
+	                                                   command,
+	                                                   NULL);
+}
+
+gchar **
+gitg_shell_run_sync_with_input_and_outputv (gboolean      preserve_line_endings,
+                                            const gchar  *input,
+                                            GError      **error,
+                                            ...)
+{
+	va_list ap;
+	gchar **ret;
+
+	va_start (ap, error);
+	ret = gitg_shell_run_sync_with_input_and_outputva (preserve_line_endings,
+	                                                   input,
+	                                                   ap,
+	                                                   error);
+	va_end (ap);
+
+	return ret;
+}
+
+GitgCommand **
+gitg_shell_parse_commands (GitgRepository  *repository,
+                           const gchar     *cmdstr,
+                           GError         **error)
+{
+	gint argc;
+	gchar **argv;
+	GitgCommand *cmd = NULL;
+	gint i;
+	GPtrArray *commands;
+	gboolean canenv = TRUE;
+	guint num = 0;
+
+	g_return_val_if_fail (repository == NULL || GITG_IS_REPOSITORY (repository), NULL);
+	g_return_val_if_fail (cmdstr != NULL, NULL);
+
+	if (!g_shell_parse_argv (cmdstr, &argc, &argv, error))
+	{
+		return FALSE;
+	}
+
+	commands = g_ptr_array_new ();
+
+	for (i = 0; i < argc; ++i)
+	{
+		gchar *pos;
+
+		if (cmd == NULL)
+		{
+			cmd = gitg_command_newv (repository, NULL);
+			g_ptr_array_add (commands, cmd);
+
+			canenv = TRUE;
+			++num;
+		}
+
+		if (strcmp (argv[i], "|") == 0)
+		{
+			cmd = NULL;
+		}
+		else if (canenv && (pos = g_utf8_strchr (argv[i], -1, '=')))
+		{
+			*pos = '\0';
+			gitg_command_add_environmentv (cmd, argv[i], pos + 1, NULL);
+		}
+		else
+		{
+			canenv = FALSE;
+			gitg_command_add_argumentsv (cmd, argv[i], NULL);
+		}
+	}
+
+	g_strfreev (argv);
+	g_ptr_array_add (commands, NULL);
+
+	return (GitgCommand **)g_ptr_array_free (commands, FALSE);
+}
+
+gboolean
+gitg_shell_run_parse (GitgShell       *shell,
+                      GitgRepository  *repository,
+                      const gchar     *cmdstr,
+                      GError         **error)
+
+{
+	gboolean ret;
+	GitgCommand **commands;
+
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+	g_return_val_if_fail (cmdstr != NULL, FALSE);
+	g_return_val_if_fail (repository == NULL || GITG_IS_REPOSITORY (repository), FALSE);
+
+	commands = gitg_shell_parse_commands (repository, cmdstr, error);
+
+	if (!commands)
+	{
+		return FALSE;
+	}
+
+	ret = run_commands (shell, commands, error);
+	g_free (commands);
+
+	return ret;
+}
+
+gboolean
+gitg_shell_run_stream (GitgShell     *shell,
+                       GInputStream  *stream,
+                       GError       **error)
+{
+	g_return_val_if_fail (GITG_IS_SHELL (shell), FALSE);
+	g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE);
+
+	gitg_io_cancel (GITG_IO (shell));
+
+	run_stream (shell, stream);
+	return TRUE;
+}
diff --git a/libgitg/gitg-shell.h b/libgitg/gitg-shell.h
new file mode 100644
index 0000000..69b0502
--- /dev/null
+++ b/libgitg/gitg-shell.h
@@ -0,0 +1,153 @@
+/*
+ * gitg-shell.h
+ * This file is part of gitg - git repository viewer
+ *
+ * Copyright (C) 2009 - Jesse van den Kieboom
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, 
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GITG_SHELL_H__
+#define __GITG_SHELL_H__
+
+#include <glib-object.h>
+#include <libgitg/gitg-io.h>
+#include <libgitg/gitg-command.h>
+#include <libgitg/gitg-repository.h>
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_SHELL			(gitg_shell_get_type ())
+#define GITG_SHELL(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_SHELL, GitgShell))
+#define GITG_SHELL_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_SHELL, GitgShell const))
+#define GITG_SHELL_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_SHELL, GitgShellClass))
+#define GITG_IS_SHELL(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_SHELL))
+#define GITG_IS_SHELL_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_SHELL))
+#define GITG_SHELL_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_SHELL, GitgShellClass))
+
+#define GITG_SHELL_ERROR		(gitg_shell_error_quark())
+
+typedef struct _GitgShell		GitgShell;
+typedef struct _GitgShellClass		GitgShellClass;
+typedef struct _GitgShellPrivate	GitgShellPrivate;
+
+struct _GitgShell
+{
+	GitgIO parent;
+
+	GitgShellPrivate *priv;
+};
+
+struct _GitgShellClass
+{
+	GitgIOClass parent_class;
+
+	/* signals */
+	void (* update)        (GitgShell           *shell,
+	                        gchar const * const *buffer);
+};
+
+GType gitg_shell_get_type                       (void) G_GNUC_CONST;
+
+GitgShell *gitg_shell_new                       (guint        buffer_size);
+GitgShell *gitg_shell_new_synchronized          (guint        buffer_size);
+
+void       gitg_shell_set_preserve_line_endings (GitgShell    *shell,
+                                                 gboolean      preserve_line_endings);
+gboolean   gitg_shell_get_preserve_line_endings (GitgShell    *shell);
+
+guint      gitg_shell_get_buffer_size           (GitgShell    *shell);
+
+GitgCommand **gitg_shell_parse_commands         (GitgRepository  *repository,
+                                                 const gchar     *cmdstr,
+                                                 GError         **error);
+
+gboolean   gitg_shell_run_parse                 (GitgShell       *shell,
+                                                 GitgRepository  *repository,
+                                                 const gchar     *cmd,
+                                                 GError         **error);
+
+gboolean   gitg_shell_runva                     (GitgShell    *shell,
+                                                 va_list       ap,
+                                                 GError      **error);
+
+gboolean   gitg_shell_run_stream                (GitgShell     *shell,
+                                                 GInputStream  *stream,
+                                                 GError       **error);
+
+gboolean   gitg_shell_run                       (GitgShell     *shell,
+                                                 GitgCommand   *command,
+                                                 GError       **error);
+
+gboolean   gitg_shell_run_list                  (GitgShell     *shell,
+                                                 GitgCommand  **commands,
+                                                 GError       **error);
+
+gboolean   gitg_shell_runv                      (GitgShell     *shell,
+                                                 GError       **error,
+                                                ...) G_GNUC_NULL_TERMINATED;
+
+gchar    **gitg_shell_run_sync_with_output      (GitgCommand  *command,
+                                                 gboolean      preserve_line_endings,
+                                                 GError      **error);
+
+gchar    **gitg_shell_run_sync_with_output_list (GitgCommand **commands,
+                                                 gboolean      preserve_line_endings,
+                                                 GError      **error);
+
+gchar    **gitg_shell_run_sync_with_outputv     (gboolean      preserve_line_endings,
+                                                 GError      **error,
+                                                 ...) G_GNUC_NULL_TERMINATED;
+
+gboolean   gitg_shell_run_sync                  (GitgCommand  *command,
+                                                 GError      **error);
+
+gboolean   gitg_shell_run_sync_list             (GitgCommand **commands,
+                                                 GError      **error);
+
+gboolean   gitg_shell_run_syncv                 (GError      **error,
+                                                 ...) G_GNUC_NULL_TERMINATED;
+
+gboolean   gitg_shell_run_sync_with_input       (GitgCommand  *command,
+                                                 const gchar  *input,
+                                                 GError      **error);
+
+gboolean   gitg_shell_run_sync_with_input_list  (GitgCommand **commands,
+                                                 const gchar  *input,
+                                                 GError      **error);
+
+gboolean   gitg_shell_run_sync_with_inputv      (const gchar  *input,
+                                                 GError      **error,
+                                                 ...) G_GNUC_NULL_TERMINATED;
+
+gchar    **gitg_shell_run_sync_with_input_and_output (GitgCommand  *command,
+                                                      gboolean      preserve_line_endings,
+                                                      const gchar  *input,
+                                                      GError      **error);
+
+gchar    **gitg_shell_run_sync_with_input_and_output_list (GitgCommand **commands,
+                                                           gboolean      preserve_line_endings,
+                                                           const gchar  *input,
+                                                           GError      **error);
+
+gchar    **gitg_shell_run_sync_with_input_and_outputv (gboolean      preserve_line_endings,
+                                                       const gchar  *input,
+                                                       GError      **error,
+                                                       ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* __GITG_SHELL_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..d7ca32d
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,12 @@
+INCLUDES = -g -I$(top_srcdir) -I$(top_srcdir)/gitg -I$(top_srcdir)/libgitg $(GITG_DEBUG_FLAGS) $(GITG_CFLAGS)
+
+noinst_PROGRAMS = $(TEST_PROGS)
+progs_ldadd     = $(top_builddir)/libgitg/libgitg-1.0.la
+
+TEST_PROGS			= shell
+shell_SOURCES			= shell.c
+shell_LDADD			= $(progs_ldadd)
+
+TESTS = $(TEST_PROGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/shell.c b/tests/shell.c
new file mode 100644
index 0000000..04d2e5b
--- /dev/null
+++ b/tests/shell.c
@@ -0,0 +1,273 @@
+#include <libgitg/gitg-shell.h>
+#include <string.h>
+
+#define test_add_repo(name, callback) g_test_add (name, RepositoryInfo, NULL, repository_setup, callback, repository_cleanup)
+
+typedef struct
+{
+	GitgRepository *repository;
+} RepositoryInfo;
+
+static gboolean
+remove_all (gchar const *path,
+            GError      **error)
+{
+	gchar const *argv[] = {
+		"rm",
+		"-rf",
+		path,
+		NULL
+	};
+
+	g_spawn_sync ("/",
+	              (gchar **)argv,
+	              NULL,
+	              G_SPAWN_SEARCH_PATH |
+	              G_SPAWN_STDOUT_TO_DEV_NULL |
+	              G_SPAWN_STDERR_TO_DEV_NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              error);
+}
+
+static void
+repository_setup (RepositoryInfo *info,
+                  gconstpointer   data)
+{
+	/* Create repository */
+	gchar const *tmp = g_get_tmp_dir ();
+	gchar *repo_path;
+	GError *error = NULL;
+
+	repo_path = g_build_filename (tmp, "gitg-test-repo", NULL);
+
+	if (g_file_test (repo_path, G_FILE_TEST_EXISTS))
+	{
+		remove_all (repo_path, &error);
+
+		g_assert_no_error (error);
+	}
+
+	g_assert (g_mkdir (repo_path, 0700) == 0);
+
+	gchar const *argv[] = {
+		"git",
+		"init",
+		NULL,
+		NULL,
+		NULL
+	};
+
+	g_spawn_sync (repo_path,
+	              (gchar **)argv,
+	              NULL,
+	              G_SPAWN_SEARCH_PATH |
+	              G_SPAWN_STDOUT_TO_DEV_NULL |
+	              G_SPAWN_STDERR_TO_DEV_NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              &error);
+
+	g_assert_no_error (error);
+
+	argv[0] = "/bin/bash";
+	argv[1] = "-c";
+	argv[2] = "echo haha > test.txt && git add test.txt && git commit -m 'Initial import'";
+
+	g_spawn_sync (repo_path,
+	              (gchar **)argv,
+	              NULL,
+	              G_SPAWN_STDOUT_TO_DEV_NULL |
+	              G_SPAWN_STDERR_TO_DEV_NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              NULL,
+	              &error);
+
+	g_assert_no_error (error);
+
+	GFile *work_tree = g_file_new_for_path (repo_path);
+	gchar *git_dir_path = g_build_filename (repo_path, ".git", NULL);
+	GFile *git_dir = g_file_new_for_path (git_dir_path);
+	g_free (git_dir_path);
+
+	info->repository = gitg_repository_new (git_dir, work_tree);
+
+	g_object_unref (work_tree);
+	g_object_unref (git_dir);
+}
+
+static void
+repository_cleanup (RepositoryInfo *info,
+                    gconstpointer   data)
+{
+	GFile *work_tree;
+	GError *error = NULL;
+
+	work_tree = gitg_repository_get_work_tree (info->repository);
+	gchar *path = g_file_get_path (work_tree);
+	g_object_unref (work_tree);
+
+	remove_all (path, &error);
+	g_free (path);
+
+	g_assert_no_error (error);
+
+	g_object_unref (info->repository);
+}
+
+static void
+test_success (RepositoryInfo *info,
+              gconstpointer   data)
+{
+	gboolean ret;
+	GError *error = NULL;
+
+	ret = gitg_shell_run_sync (gitg_command_newv (info->repository,
+	                                              "rev-parse",
+	                                              "HEAD",
+	                                              NULL),
+	                           &error);
+
+	g_assert_no_error (error);
+	g_assert (ret);
+}
+
+static void
+test_fail (RepositoryInfo *info,
+           gconstpointer   data)
+{
+	gboolean ret;
+	GError *error = NULL;
+
+	ret = gitg_shell_run_sync (gitg_command_newv (info->repository,
+	                                              "bogus",
+	                                              NULL),
+	                           &error);
+
+	g_assert (!ret);
+	g_assert (error != NULL);
+
+	g_error_free (error);
+}
+
+static void
+test_output (RepositoryInfo *info,
+             gconstpointer   data)
+{
+	gchar **ret;
+	GError *error = NULL;
+
+	ret = gitg_shell_run_sync_with_output (gitg_command_newv (info->repository,
+	                                                          "rev-parse",
+	                                                          "HEAD",
+	                                                          NULL),
+	                                       FALSE,
+	                                       &error);
+
+	g_assert_no_error (error);
+
+	g_assert (ret);
+	g_assert (g_strv_length (ret) == 1);
+
+	g_assert (strlen (ret[0]) == 40);
+}
+
+static void
+test_input (void)
+{
+	gchar **ret;
+	gchar const *input = "Hello world";
+	GError *error = NULL;
+
+	ret = gitg_shell_run_sync_with_input_and_output (gitg_command_newv (NULL,
+	                                                                    "cat",
+	                                                                    "-",
+	                                                                    NULL),
+	                                                 FALSE,
+	                                                 input,
+	                                                 &error);
+
+	g_assert_no_error (error);
+	g_assert (ret);
+
+	g_assert (g_strv_length (ret) == 1);
+	g_assert_cmpstr (ret[0], ==, input);
+}
+
+static void
+test_pipe (void)
+{
+	gchar **ret;
+	GError *error = NULL;
+	gchar const *input = "Hello world";
+
+	ret = gitg_shell_run_sync_with_outputv (FALSE,
+	                                        &error,
+	                                        gitg_command_newv (NULL, "echo", input, NULL),
+	                                        gitg_command_newv (NULL, "cat", "-", NULL),
+	                                        NULL);
+
+	g_assert_no_error (error);
+	g_assert (ret);
+
+	g_assert (g_strv_length (ret) == 1);
+	g_assert_cmpstr (ret[0], ==, input);
+}
+
+static void
+test_pipestr (void)
+{
+	gchar **ret;
+	GError *error = NULL;
+	gchar const *input = "Hello world";
+	gchar *cmdstr;
+	GitgCommand **commands;
+
+	cmdstr = g_strconcat ("echo '", input, "' | cat -", NULL);
+
+	commands = gitg_shell_parse_commands (NULL, cmdstr, &error);
+
+	g_assert_no_error (error);
+	g_assert (commands);
+
+	ret = gitg_shell_run_sync_with_output_list (commands,
+	                                            FALSE,
+	                                            &error);
+
+	g_assert_no_error (error);
+	g_assert (ret);
+
+	g_assert (g_strv_length (ret) == 1);
+	g_assert_cmpstr (ret[0], ==, input);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	gitg_debug_init ();
+
+	test_add_repo ("/shell/success", test_success);
+	test_add_repo ("/shell/fail", test_fail);
+
+	test_add_repo ("/shell/output", test_output);
+
+	g_test_add_func ("/shell/input", test_input);
+	g_test_add_func ("/shell/pipe", test_pipe);
+	g_test_add_func ("/shell/pipestr", test_pipestr);
+
+	return g_test_run ();
+}
+/* ex:ts=8:noet: */
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..9c6e256
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,10 @@
+INCLUDES = -g -I$(top_srcdir) -I$(top_srcdir)/gitg -I$(top_srcdir)/libgitg $(GITG_DEBUG_FLAGS) $(GITG_CFLAGS)
+
+noinst_PROGRAMS = $(TOOLS_PROGS)
+tools_ldadd     = $(top_builddir)/libgitg/libgitg-1.0.la
+
+TOOLS_PROGS			= gitg-shell
+gitg_shell_SOURCES		= gitg-shell.c
+gitg_shell_LDADD		= $(tools_ldadd)
+
+-include $(top_srcdir)/git.mk
diff --git a/tools/gitg-shell.c b/tools/gitg-shell.c
new file mode 100644
index 0000000..16ae312
--- /dev/null
+++ b/tools/gitg-shell.c
@@ -0,0 +1,206 @@
+#include <glib.h>
+#include <stdlib.h>
+#include <libgitg/gitg-shell.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <libgitg/gitg-debug.h>
+
+static gchar *repository_path = NULL;
+
+static GOptionEntry entries[] =
+{
+	{ "repository", 'r', 0, G_OPTION_ARG_FILENAME, &repository_path, "Repository path" },
+	{ NULL }
+};
+
+static GFile *
+find_git_dir (GFile *work_tree)
+{
+	GFile *ret;
+
+	work_tree = g_file_dup (work_tree);
+
+	while (work_tree)
+	{
+		ret = g_file_get_child (work_tree, ".git");
+
+		if (g_file_query_exists (ret, NULL))
+		{
+			g_object_unref (work_tree);
+			return ret;
+		}
+		else
+		{
+			GFile *tmp;
+
+			tmp = g_file_get_parent (work_tree);
+			g_object_unref (work_tree);
+
+			work_tree = tmp;
+		}
+	}
+
+	return NULL;
+}
+
+static void
+parse_options (int *argc,
+               char ***argv)
+{
+	GError *error = NULL;
+	GOptionContext *context;
+
+	context = g_option_context_new ("- git shell tool");
+
+	g_option_context_set_ignore_unknown_options (context, TRUE);
+	g_option_context_add_main_entries (context, entries, "gitg");
+
+	if (!g_option_context_parse (context, argc, argv, &error))
+	{
+		g_print ("option parsing failed: %s\n", error->message);
+		g_error_free (error);
+
+		exit (1);
+	}
+
+	g_option_context_free (context);
+}
+
+static void
+on_shell_end (GitgShell *shell,
+              GError    *error,
+              GMainLoop *loop)
+{
+	g_main_loop_quit (loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+	GitgRepository *repository;
+	GFile *work_tree;
+	GFile *git_dir;
+	gint i;
+	GString *cmdstr;
+	gchar *cs;
+	GitgCommand **commands;
+	GitgShell *shell;
+	GMainLoop *loop;
+	GError *error = NULL;
+	GInputStream *input;
+	GOutputStream *output;
+
+	g_type_init ();
+
+	parse_options (&argc, &argv);
+
+	gitg_debug_init ();
+
+	if (i == 1)
+	{
+		g_print ("Please specify a command...\n");
+		return 1;
+	}
+
+	if (!repository_path)
+	{
+		gchar *path;
+		GFile *file;
+
+		path = g_get_current_dir ();
+		file = g_file_new_for_path (path);
+
+		git_dir = find_git_dir (file);
+		g_free (path);
+		g_object_unref (file);
+
+		if (git_dir)
+		{
+			work_tree = g_file_get_parent (git_dir);
+		}
+	}
+	else
+	{
+		work_tree = g_file_new_for_commandline_arg (repository_path);
+		git_dir = find_git_dir (work_tree);
+	}
+
+	if (!git_dir)
+	{
+		g_print ("Could not find git dir...\n");
+		return 1;
+	}
+
+	repository = gitg_repository_new (git_dir, work_tree);
+
+	g_object_unref (work_tree);
+	g_object_unref (git_dir);
+
+	cmdstr = g_string_new ("");
+
+	/* Create commands */
+	for (i = 1; i < argc; ++i)
+	{
+		gchar *quoted;
+
+		if (strcmp (argv[i], "!") == 0)
+		{
+			quoted = g_strdup ("|");
+		}
+		else
+		{
+			quoted = g_shell_quote (argv[i]);
+		}
+
+		if (i != 1)
+		{
+			g_string_append_c (cmdstr, ' ');
+		}
+
+		g_string_append (cmdstr, quoted);
+	}
+
+	cs = g_string_free (cmdstr, FALSE);
+	g_print ("Running: %s\n\n", cs);
+
+	commands = gitg_shell_parse_commands (repository, cs, &error);
+
+	g_free (cs);
+	g_object_unref (repository);
+
+	if (error)
+	{
+		g_print ("Could not parse arguments: %s\n", error->message);
+		g_error_free (error);
+
+		return 1;
+	}
+
+	loop = g_main_loop_new (NULL, FALSE);
+	shell = gitg_shell_new (1000);
+
+	input = g_unix_input_stream_new (STDIN_FILENO, TRUE);
+	output = g_unix_output_stream_new (STDOUT_FILENO, TRUE);
+
+	gitg_io_set_input (GITG_IO (shell), input);
+	gitg_io_set_output (GITG_IO (shell), output);
+
+	g_signal_connect (shell,
+	                  "end",
+	                  G_CALLBACK (on_shell_end),
+	                  loop);
+
+	if (!gitg_shell_run_list (shell, commands, &error))
+	{
+		g_print ("Error launching shell: %s\n", error->message);
+		return 1;
+	}
+
+	g_free (commands);
+
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+	g_object_unref (shell);
+
+	return 0;
+}



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