diff --git a/Makefile.am b/Makefile.am index 4e97269c..a3a9d144 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 5aa6cd5a..abdba9ad 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 17ef96e6..12da232d 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 58b7f183..88fac6ab 100644 --- a/gitg/gitg-branch-actions.h +++ b/gitg/gitg-branch-actions.h @@ -24,28 +24,28 @@ #define __GITG_BRANCH_ACTIONS_H__ #include -#include "gitg-window.h" +#include 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 38bc0448..8b844a9d 100644 --- a/gitg/gitg-commit-view.c +++ b/gitg/gitg-commit-view.c @@ -26,6 +26,7 @@ #include #include #include +#include #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 b5e41bef..e393c1a5 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 c1dc6ac9..aceebd9f 100644 --- a/gitg/gitg-repository-dialog.c +++ b/gitg/gitg-repository-dialog.c @@ -24,6 +24,7 @@ #include #include +#include #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 bf58635d..0aef963c 100644 --- a/gitg/gitg-revision-changes-panel.c +++ b/gitg/gitg-revision-changes-panel.c @@ -6,12 +6,14 @@ #include #include #include -#include +#include #include #include "gitg-diff-view.h" #include "gitg-utils.h" +#include "gitg-preferences.h" #include + #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 67c17d77..f8791312 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 985cd1cb..94f15347 100644 --- a/gitg/gitg-revision-files-panel.c +++ b/gitg/gitg-revision-files-panel.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #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 5136d65c..7e0f012f 100644 --- a/gitg/gitg-window.c +++ b/gitg/gitg-window.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #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 648f38e0..3245013f 100644 --- a/gitg/gitg-window.h +++ b/gitg/gitg-window.h @@ -25,14 +25,15 @@ #include #include +#include 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 cc0c2e8a..a6f65725 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 00000000..7395b256 --- /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 00000000..f31c2c6b --- /dev/null +++ b/libgitg/gitg-command.h @@ -0,0 +1,76 @@ +#ifndef __GITG_COMMAND_H__ +#define __GITG_COMMAND_H__ + +#include +#include + +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 7ad40487..12f51839 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 -#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_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_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); + gchar *head = gitg_repository_parse_head (commit->priv->repository); + gitg_io_cancel (GITG_IO (shell)); - 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); + 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); + gitg_io_cancel (GITG_IO (shell)); - 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); + 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_io_cancel (GITG_IO (shell)); - gitg_repository_run_commandv(commit->priv->repository, commit->priv->runner, NULL, "ls-files", "--others", "--exclude-standard", NULL); + 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); - else - refresh_done(commit->priv->runner, FALSE, commit); -} - -static void -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); - - 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); - - 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_changes(file, gitg_changed_file_get_changes(file) | GITG_CHANGED_FILE_CHANGES_CACHED); - update_changed_file_status(file, parts[4], parts[0] + 1); + update_index (commit); } else { - gitg_changed_file_set_changes(file, gitg_changed_file_get_changes(file) & ~GITG_CHANGED_FILE_CHANGES_CACHED); + refresh_done (commit->priv->shell, FALSE, commit); + } +} + +static void +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_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); + + if (!ret) + { + return; + } + + gchar **parts = *ret ? g_strsplit_set (*ret, " \t", 0) : NULL; + g_strfreev (ret); + + 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_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); } 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); return NULL; } - gchar **email = gitg_repository_command_with_outputv(commit->priv->repository, NULL, "config", "--get", "user.email", NULL); + if (!*user || !**user) + { + g_strfreev (user); + return 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); + 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 (runner); + g_free (head); + g_free (fullcomment); + g_free (converted); + g_object_unref (command); - if (buffer->len != GITG_HASH_SHA_SIZE) + 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, GitgChangedFile *file, gchar const *hunk, GError **error) +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_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); + 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); + 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); + g_free (path); - update_index_file(commit, file); - update_index_unstaged(commit, file); - g_object_unref(f); + 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); + error); - g_object_unref (work_tree); - - update_index_file(commit, file); - update_index_unstaged(commit, file); - - 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 27e25875..66a62d19 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); +GQuark gitg_commit_error_quark (void); -GType gitg_commit_get_type(void) G_GNUC_CONST; -GitgCommit *gitg_commit_new(GitgRepository *repository); +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); +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_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_revert(GitgCommit *commit, GitgChangedFile *file, gchar const *hunk, GError **error); -gboolean gitg_commit_add_ignore(GitgCommit *commit, GitgChangedFile *file, GError **error); +gboolean gitg_commit_undo (GitgCommit *commit, + GitgChangedFile *file, + gchar const *hunk, + GError **error); -GitgChangedFile *gitg_commit_find_changed_file(GitgCommit *commit, GFile *file); +gboolean gitg_commit_add_ignore (GitgCommit *commit, + GitgChangedFile *file, + GError **error); -gchar *gitg_commit_amend_message (GitgCommit *commit); +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 8a8b30a1..e0e59343 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_shell_run (config->priv->shell, + gitg_command_newv (config->priv->repository, + "config", + "--global", + key, + NULL), + NULL); - gboolean ret = gitg_runner_run (config->priv->runner, argv, 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_shell_run (config->priv->shell, + gitg_command_newv (config->priv->repository, + "config", + "--global", + "--get-regexp", + NULL), + NULL); - gboolean ret = gitg_runner_run (config->priv->runner, argv, 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 0fb61d04..a68363e6 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 00000000..2300adb4 --- /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 00000000..f240d164 --- /dev/null +++ b/libgitg/gitg-io.h @@ -0,0 +1,70 @@ +#ifndef __GITG_IO_H__ +#define __GITG_IO_H__ + +#include +#include + +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 00000000..1efe4ca2 --- /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 00000000..5f823173 --- /dev/null +++ b/libgitg/gitg-line-parser.h @@ -0,0 +1,45 @@ +#ifndef __GITG_LINE_PARSER_H__ +#define __GITG_LINE_PARSER_H__ + +#include + +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 3d4b1cd5..303750af 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 #include @@ -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 01f4ed02..158ab237 100644 --- a/libgitg/gitg-repository.h +++ b/libgitg/gitg-repository.h @@ -26,7 +26,6 @@ #include #include -#include #include 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 0b4371e4..dc195472 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 @@ -31,56 +9,33 @@ #include #include -#include #include #include #define GITG_RUNNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_RUNNER, GitgRunnerPrivate)) -/* Signals */ -enum +struct _GitgRunnerPrivate { - BEGIN_LOADING, - UPDATE, - END_LOADING, - LAST_SIGNAL + GitgCommand *command; + + GInputStream *stdout; + GOutputStream *stdin; + + GCancellable *cancellable; + gboolean cancelled; + + GPid pid; + guint watch_id; }; -static guint runner_signals[LAST_SIGNAL] = { 0 }; +G_DEFINE_TYPE (GitgRunner, gitg_runner, GITG_TYPE_IO) -/* Properties */ enum { PROP_0, - - PROP_BUFFER_SIZE, - PROP_SYNCHRONIZED, - PROP_PRESERVE_LINE_ENDINGS + PROP_COMMAND }; -struct _GitgRunnerPrivate -{ - GPid pid; - GInputStream *input_stream; - GOutputStream *output_stream; - GCancellable *cancellable; - - guint buffer_size; - gchar *read_buffer; - gchar **lines; - gchar **environment; - - gchar *rest_buffer; - gssize rest_buffer_size; - - gint exit_status; - - guint synchronized : 1; - guint preserve_line_endings : 1; -}; - -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,104 +62,57 @@ async_data_free (AsyncData *data) g_slice_free (AsyncData, data); } -GQuark -gitg_runner_error_quark (void) -{ - static GQuark quark = 0; - - if (G_UNLIKELY (quark == 0)) - { - quark = g_quark_from_string ("gitg_runner_error"); - } - - return quark; -} - -static void -runner_io_exit (GPid pid, - gint status, - GitgRunner *runner) -{ - g_spawn_close_pid (pid); - - if (runner->priv->pid) - { - runner->priv->pid = 0; - runner->priv->exit_status = status; - } -} - -static void -free_lines (GitgRunner *runner) -{ - gint i = 0; - - while (runner->priv->lines[i]) - { - g_free (runner->priv->lines[i++]); - } - - runner->priv->lines[0] = NULL; -} - static void gitg_runner_finalize (GObject *object) { - GitgRunner *runner = GITG_RUNNER (object); - - /* Cancel possible running */ - gitg_runner_cancel (runner); - - /* Free potential stored lines */ - free_lines (runner); - - /* 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); - - /* Remove line buffer */ - g_free (runner->priv->rest_buffer); - g_strfreev (runner->priv->environment); - - g_object_unref (runner->priv->cancellable); - G_OBJECT_CLASS (gitg_runner_parent_class)->finalize (object); } static void -gitg_runner_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +close_streams (GitgRunner *runner) { - GitgRunner *runner = GITG_RUNNER (object); - - switch (prop_id) + if (runner->priv->cancellable) { - 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); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; + g_cancellable_cancel (runner->priv->cancellable); } + + if (runner->priv->stdin != NULL) + { + g_output_stream_close (runner->priv->stdin, NULL, NULL); + g_object_unref (runner->priv->stdin); + + runner->priv->stdin = NULL; + } + + if (runner->priv->stdout != NULL) + { + g_input_stream_close (runner->priv->stdout, NULL, NULL); + g_object_unref (runner->priv->stdout); + + runner->priv->stdout = NULL; + } + + gitg_io_close (GITG_IO (runner)); } static void -set_buffer_size (GitgRunner *runner, - guint buffer_size) +gitg_runner_dispose (GObject *object) { - runner->priv->buffer_size = buffer_size; - runner->priv->lines = g_slice_alloc (sizeof (gchar *) * (runner->priv->buffer_size + 1)); - runner->priv->lines[0] = NULL; + GitgRunner *runner; - runner->priv->read_buffer = g_slice_alloc (sizeof (gchar) * (runner->priv->buffer_size + 1)); + runner = GITG_RUNNER (object); + + if (runner->priv->command != NULL) + { + g_object_unref (runner->priv->command); + runner->priv->command = NULL; + } + + gitg_io_cancel (GITG_IO (runner)); + + close_streams (runner); + + G_OBJECT_CLASS (gitg_runner_parent_class)->dispose (object); } static void @@ -211,652 +121,38 @@ gitg_runner_set_property (GObject *object, const 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: + gitg_runner_set_command (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gitg_runner_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GitgRunner *self = GITG_RUNNER (object); + + switch (prop_id) + { + case PROP_COMMAND: + g_value_set_object (value, self->priv->command); break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } } -static void -gitg_runner_class_init (GitgRunnerClass *klass) -{ - 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) -{ - 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) - { - 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 (GitgRunner *runner, - gchar *buffer, - gssize size) -{ - gchar *ptr; - gchar *newline = NULL; - gint i = 0; - gchar *all; - gchar *end; - - free_lines (runner); - - if (runner->priv->rest_buffer_size > 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); - - all = g_string_free (str, FALSE); - size += runner->priv->rest_buffer_size; - - g_free (runner->priv->rest_buffer); - runner->priv->rest_buffer = NULL; - runner->priv->rest_buffer_size = 0; - } - else - { - all = buffer; - } - - ptr = all; - - gchar *line_end; - end = ptr + size; - - while ((newline = find_newline (ptr, end, &line_end))) - { - 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; - } - - if (ptr < end) - { - runner->priv->rest_buffer_size = end - ptr; - runner->priv->rest_buffer = g_strndup (ptr, runner->priv->rest_buffer_size); - } - - runner->priv->lines[i] = NULL; - - g_signal_emit (runner, runner_signals[UPDATE], 0, runner->priv->lines); - - if (all != buffer) - { - g_free (all); - } -} - -static void -close_streams (GitgRunner *runner) -{ - 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; - } - - 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; - } - - g_free (runner->priv->rest_buffer); - runner->priv->rest_buffer = NULL; - runner->priv->rest_buffer_size = 0; -} - -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'; - } - - gchar *b[] = {runner->priv->rest_buffer, NULL}; - - g_signal_emit (runner, runner_signals[UPDATE], 0, b); - } -} - -static gboolean -run_sync (GitgRunner *runner, - gchar const *input, - GError **error) -{ - 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; -} - -static void -async_failed (AsyncData *data) -{ - 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); -} - -static void start_reading (GitgRunner *runner, AsyncData *data); - -static void -read_output_ready (GInputStream *stream, - GAsyncResult *result, - AsyncData *data) -{ - GError *error = NULL; - - gssize read = g_input_stream_read_finish (stream, 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); - } - - return; - } - - if (read == -1) - { - g_input_stream_close (stream, NULL, NULL); - async_failed (data); - - if (error) - { - g_error_free (error); - } - - return; - } - - 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 - { - 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); - } -} - -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); -} - -static void -write_input_ready (GOutputStream *stream, GAsyncResult *result, AsyncData *data) -{ - GError *error = NULL; - g_output_stream_write_finish (stream, result, &error); - - if (g_cancellable_is_cancelled (data->cancellable)) - { - if (error) - { - g_error_free (error); - } - - async_data_free (data); - } - - if (error) - { - async_failed (data); - g_error_free (error); - } - else - { - start_reading (data->runner, data); - } -} - -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) - { - runner->priv->output_stream = g_object_ref (output_stream); - } - - 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); - } - - /* Emit begin-loading signal */ - g_signal_emit (runner, runner_signals[BEGIN_LOADING], 0); - - if (runner->priv->synchronized) - { - return run_sync (runner, input, 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); - } - } - return TRUE; -} - -gboolean -gitg_runner_run_with_arguments (GitgRunner *runner, - GFile *work_tree, - gchar const **argv, - gchar const *input, - GError **error) -{ - g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE); - - gint stdoutf; - gint stdinf; - - gitg_runner_cancel (runner); - gchar *wd = NULL; - - if (work_tree) - { - wd = g_file_get_path (work_tree); - } - - 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) - { - runner->priv->pid = 0; - return FALSE; - } - - GOutputStream *output_stream = NULL; - GInputStream *input_stream; - - if (input) - { - output_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf, - TRUE)); - } - - input_stream = G_INPUT_STREAM (g_unix_input_stream_new (stdoutf, TRUE)); - - ret = gitg_runner_run_streams (runner, - input_stream, - output_stream, - input, - error); - - if (output_stream) - { - g_object_unref (output_stream); - } - - g_object_unref (input_stream); - - return ret; -} - -gboolean -gitg_runner_run (GitgRunner *runner, - gchar const **argv, - GError **error) -{ - return gitg_runner_run_with_arguments (runner, NULL, argv, NULL, error); -} - -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; -} - static void dummy_cb (GPid pid, gint status, @@ -864,107 +160,379 @@ dummy_cb (GPid pid, { } -void -gitg_runner_cancel (GitgRunner *runner) +static void +kill_process (GitgRunner *runner) { - g_return_if_fail (GITG_IS_RUNNER (runner)); - - if (runner->priv->input_stream) + if (runner->priv->pid == 0) { - g_cancellable_cancel (runner->priv->cancellable); - g_object_unref (runner->priv->cancellable); - - 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_io_exit (runner->priv->pid, EXIT_FAILURE, runner); - } - - close_streams (runner); - g_signal_emit (runner, runner_signals[END_LOADING], 0, TRUE); + return; } + + /* 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); + + g_child_watch_add (runner->priv->pid, dummy_cb, NULL); + + runner->priv->pid = 0; + + gitg_io_set_exit_status (GITG_IO (runner), EXIT_FAILURE); } -gboolean -gitg_runner_running (GitgRunner *runner) +static void +runner_done (GitgRunner *runner, + GError *error) { - g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE); - return runner->priv->input_stream != NULL; -} + close_streams (runner); + kill_process (runner); -gint -gitg_runner_get_exit_status (GitgRunner *runner) -{ - g_return_val_if_fail (GITG_IS_RUNNER (runner), 1); - - return runner->priv->exit_status; -} - -void -gitg_runner_set_environment (GitgRunner *runner, - gchar const **environment) -{ - g_return_if_fail (GITG_IS_RUNNER (runner)); - - g_strfreev (runner->priv->environment); - - if (environment == NULL) + if (!error && gitg_io_get_exit_status (GITG_IO (runner)) != 0) { - runner->priv->environment = NULL; + GError *err; + + 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))); + + gitg_io_end (GITG_IO (runner), err); + g_error_free (err); } else { - gint len = g_strv_length ((gchar **)environment); + gitg_io_end (GITG_IO (runner), error); + } +} - runner->priv->environment = g_new (gchar *, len + 1); - gint i; +static void +gitg_runner_cancel (GitgIO *io) +{ + gboolean was_running; + GitgRunner *runner; - for (i = 0; i < len; ++i) + runner = GITG_RUNNER (io); + + if (runner->priv->cancellable) + { + g_cancellable_cancel (runner->priv->cancellable); + + g_object_unref (runner->priv->cancellable); + runner->priv->cancellable = NULL; + } + + was_running = gitg_io_get_running (GITG_IO (runner)); + + GITG_IO_CLASS (gitg_runner_parent_class)->cancel (GITG_IO (runner)); + + if (was_running) + { + runner_done (runner, NULL); + } +} + +static void +gitg_runner_class_init (GitgRunnerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GitgIOClass *io_class = GITG_IO_CLASS (klass); + + object_class->finalize = gitg_runner_finalize; + object_class->dispose = gitg_runner_dispose; + + object_class->get_property = gitg_runner_get_property; + object_class->set_property = gitg_runner_set_property; + + io_class->cancel = gitg_runner_cancel; + + g_type_class_add_private (object_class, sizeof(GitgRunnerPrivate)); + + 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 void +gitg_runner_init (GitgRunner *self) +{ + self->priv = GITG_RUNNER_GET_PRIVATE (self); +} + +GitgRunner * +gitg_runner_new (GitgCommand *command) +{ + return g_object_new (GITG_TYPE_RUNNER, + "command", command, + NULL); +} + +static void +splice_input_ready_cb (GOutputStream *source, + GAsyncResult *result, + AsyncData *data) +{ + GError *error = NULL; + gboolean ret; + + ret = g_output_stream_splice_finish (source, result, &error); + + if (g_cancellable_is_cancelled (data->cancellable)) + { + if (error) { - runner->priv->environment[i] = g_strdup (environment[i]); + g_error_free (error); } - runner->priv->environment[len] = NULL; + async_data_free (data); + return; + } + + if (!ret) + { + runner_done (data->runner, error); + } + + if (error) + { + g_error_free (error); + } + + async_data_free (data); +} + +static void +splice_output_ready_cb (GOutputStream *source, + GAsyncResult *result, + AsyncData *data) +{ + GError *error = NULL; + gboolean ret; + + ret = g_output_stream_splice_finish (source, result, &error); + + if (g_cancellable_is_cancelled (data->cancellable)) + { + if (error) + { + g_error_free (error); + } + + async_data_free (data); + return; + } + + if (!ret) + { + runner_done (data->runner, error); + } + else if (data->runner->priv->pid == 0) + { + runner_done (data->runner, NULL); + } + + if (error) + { + g_error_free (error); + } + + async_data_free (data); +} + +void +gitg_runner_stream_close (GitgRunner *runner, + GError *error) +{ + g_return_if_fail (GITG_IS_RUNNER (runner)); + + if (runner->priv->pid == 0 || error) + { + runner_done (runner, error); + } + else + { + g_input_stream_close (runner->priv->stdout, NULL, NULL); + } +} + +static void +process_watch_cb (GPid pid, + gint status, + GitgRunner *runner) +{ + runner->priv->pid = 0; + + if (WIFEXITED (status)) + { + gitg_io_set_exit_status (GITG_IO (runner), WEXITSTATUS (status)); + } + else + { + gitg_io_set_exit_status (GITG_IO (runner), 0); + } + + /* 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) + { + g_object_unref (runner->priv->cancellable); + runner->priv->cancellable = NULL; + } + + runner->priv->watch_id = 0; + + if (runner->priv->stdout == NULL || g_input_stream_is_closed (runner->priv->stdout)) + { + runner_done (runner, NULL); } } void -gitg_runner_add_environment (GitgRunner *runner, - gchar const *key, - gchar const *value) +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)); - g_return_if_fail (key != NULL); - g_return_if_fail (value != NULL); - if (runner->priv->environment == NULL) + gitg_io_cancel (GITG_IO (runner)); + + runner->priv->cancelled = FALSE; + + working_directory = gitg_command_get_working_directory (runner->priv->command); + + if (working_directory) { - 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; + wd_path = g_file_get_path (working_directory); + g_object_unref (working_directory); } - gint len = g_strv_length (runner->priv->environment); - runner->priv->environment = g_realloc (runner->priv->environment, - sizeof (gchar *) * (len + 2)); + start_input = gitg_io_get_input (GITG_IO (runner)); - runner->priv->environment[len] = g_strconcat (key, "=", value, NULL); - runner->priv->environment[len + 1] = NULL; + 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); + + g_free (wd_path); + + gitg_io_begin (GITG_IO (runner)); + + if (!ret) + { + runner_done (runner, error); + g_error_free (error); + return; + } + + runner->priv->watch_id = g_child_watch_add (runner->priv->pid, + (GChildWatchFunc)process_watch_cb, + runner); + + if (start_input) + { + AsyncData *data; + + runner->priv->cancellable = g_cancellable_new (); + + runner->priv->stdin = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf, + TRUE)); + + data = async_data_new (runner); + + /* 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); + } + + output = G_INPUT_STREAM (g_unix_input_stream_new (stdoutf, + TRUE)); + + smart = gitg_smart_charset_converter_new (gitg_encoding_get_candidates ()); + + runner->priv->stdout = g_converter_input_stream_new (output, + G_CONVERTER (smart)); + + g_object_unref (smart); + g_object_unref (output); + + end_output = gitg_io_get_output (GITG_IO (runner)); + + if (end_output) + { + AsyncData *data; + + if (runner->priv->cancellable == NULL) + { + runner->priv->cancellable = g_cancellable_new (); + } + + 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_set_command (GitgRunner *runner, GitgCommand *command) +{ + g_return_if_fail (GITG_IS_RUNNER (runner)); + g_return_if_fail (GITG_IS_COMMAND (command)); + + if (runner->priv->command) + { + g_object_unref (runner->priv->command); + } + + 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); + + return runner->priv->command; } diff --git a/libgitg/gitg-runner.h b/libgitg/gitg-runner.h index 589ffe10..8b02db4d 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 +#include +#include #include 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); +GitgRunner *gitg_runner_new (GitgCommand *command); -guint gitg_runner_get_buffer_size (GitgRunner *runner); +void gitg_runner_run (GitgRunner *runner); -gboolean gitg_runner_run_stream (GitgRunner *runner, - GInputStream *stream, - GError **error); +GitgCommand *gitg_runner_get_command (GitgRunner *runner); +void gitg_runner_set_command (GitgRunner *runner, GitgCommand *command); -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); - -void gitg_runner_set_preserve_line_endings (GitgRunner *runner, - gboolean preserve_line_endings); - -gboolean gitg_runner_get_preserve_line_endings (GitgRunner *runner); - -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 00000000..9d8937be --- /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 +#include +#include +#include +#include + +#include +#include +#include + +#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 00000000..69b05024 --- /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 +#include +#include +#include + +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 00000000..d7ca32de --- /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 00000000..04d2e5bb --- /dev/null +++ b/tests/shell.c @@ -0,0 +1,273 @@ +#include +#include + +#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 00000000..9c6e256e --- /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 00000000..16ae3124 --- /dev/null +++ b/tools/gitg-shell.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include + +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; +}