[gitg] Implemented push, merge, rebase, stash by dnd



commit 9ca7a51589f0d3006a029750f0a451d76ac80257
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sun Jul 5 02:12:08 2009 +0200

    Implemented push, merge, rebase, stash by dnd

 gitg/gitg-branch-actions.c    |  608 +++++++++++++++++++++++++++++++++++++----
 gitg/gitg-branch-actions.h    |    1 +
 gitg/gitg-menus.xml           |   17 +-
 gitg/gitg-ref.c               |    7 +-
 gitg/gitg-repository-dialog.c |    1 +
 gitg/gitg-repository.c        |   45 +++
 gitg/gitg-repository.h        |    2 +
 gitg/gitg-runner.c            |   59 ++++-
 gitg/gitg-runner.h            |    3 +
 gitg/gitg-spinner.c           |    2 +-
 gitg/gitg-window.c            |  161 ++++++++++-
 11 files changed, 824 insertions(+), 82 deletions(-)
---
diff --git a/gitg/gitg-branch-actions.c b/gitg/gitg-branch-actions.c
index 4d7fdd5..0a13e94 100644
--- a/gitg/gitg-branch-actions.c
+++ b/gitg/gitg-branch-actions.c
@@ -1,6 +1,8 @@
 #include <glib/gi18n.h>
+#include <unistd.h>
 
 #include "gitg-branch-actions.h"
+#include "gitg-utils.h"
 
 typedef enum
 {
@@ -306,7 +308,6 @@ remove_remote_branch (GitgWindow *window,
                       GitgRef    *ref)
 {
 	gchar const *name = gitg_ref_get_shortname (ref);
-	GitgRepository *repository = gitg_window_get_repository (window);
 
 	gint r = message_dialog (window,
 	                         GTK_MESSAGE_QUESTION,
@@ -367,62 +368,260 @@ gitg_branch_actions_remove (GitgWindow *window,
 	return ret;
 }
 
+static void
+reset_buffer (GitgRunner *runner, GString *buffer)
+{
+	g_string_erase (buffer, 0, -1);
+}
+
+static void
+update_buffer (GitgRunner *runner, gchar **lines, GString *buffer)
+{
+	gchar **ptr = lines;
+	
+	while (ptr && *ptr)
+	{
+		if (buffer->len != 0)
+		{
+			g_string_append_c (buffer, '\n');
+		}
+		
+		g_string_append (buffer, *ptr);		
+		++ptr;
+	}
+}
+
 static gboolean
-stash_changes (GitgWindow *window,
-               GitgRef    *ref)
+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);
+}
+
+static gboolean
+stash_changes_real (GitgWindow *window, gchar **ref, gboolean storeref)
 {
 	GitgRepository *repository = gitg_window_get_repository (window);
+	gboolean ret;
+	gchar *tree = NULL;
+	gchar *commit = NULL;
+	gchar *head = NULL;
+	gboolean showerror = FALSE;
+
+	GitgRunner *runner = gitg_runner_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);
+	
+	gchar const *secondary;
+	
+	if (storeref)
+	{
+		secondary = _("Do you want to temporarily stash these changes?");
+	}
+	else
+	{
+		secondary = _("Do you want to stash and reapply these changes?");
+	}
 	
-	gchar **output = gitg_repository_command_with_outputv (repository,
-	                                                       NULL,
-	                                                       "diff-files",
-	                                                       NULL);
+	gint r = message_dialog (window,
+	                         GTK_MESSAGE_QUESTION,
+	                         _("You have uncommited changes in your current working tree"),
+	                         secondary,
+	                         _("Stash changes"));
 
-	if (output && *output && **output)
+	if (r != GTK_RESPONSE_ACCEPT)
 	{
-		gint ret = message_dialog (window,
-		                           GTK_MESSAGE_QUESTION,
-		                           _("You have uncommited changes in your current working copy"),
-		                           _("Do you want to temporarily stash these changes?"),
-		                           _("Stash changes"));
+		ret = FALSE;
+		goto cleanup;
+	}
+	
+	// Create tree object of the current index
+	gitg_repository_run_commandv (repository, runner, NULL,  
+	                              "write-tree", NULL);
+	
+	if (buffer->len == 0)
+	{
+		ret = FALSE;
+		showerror = TRUE;
+		goto cleanup;
+	}
+	
+	tree = g_strndup (buffer->str, buffer->len);
+	head = gitg_repository_parse_head (repository);
+	
+	gitg_repository_run_commandv (repository, runner, NULL, 
+	                              "commit-tree", tree, "-p", head, NULL);
 
-		if (ret != GTK_RESPONSE_ACCEPT)
-		{
-			return FALSE;
-		}
+	if (buffer->len == 0)
+	{
+		ret = FALSE;
+		showerror = TRUE;
+		goto cleanup;
+	}
+	
+	commit = g_strndup (buffer->str, buffer->len);
+	
+	// Working tree
+	gchar *tmpname = NULL;
+	gint fd = g_file_open_tmp ("gitg-temp-index-XXXXXX", &tmpname, NULL);
+	
+	if (fd == -1)
+	{
+		ret = FALSE;
+		showerror = TRUE;
+		goto cleanup;
+	}
+	
+	GFile *customindex = g_file_new_for_path (tmpname);
+	g_free (tmpname);
+	
+	close (fd);
+	
+	gchar const *gitdir = gitg_repository_get_path (repository);	
+	gchar *indexpath = g_build_filename (gitdir, ".git", "index", NULL);
+
+	GFile *index = g_file_new_for_path (indexpath);
+	g_free (indexpath);
+	
+	gboolean copied = g_file_copy (index, customindex, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL);
+	g_object_unref (index);
+
+	if (!copied)
+	{
+		g_object_unref (customindex);
+
+		ret = FALSE;
+		showerror = TRUE;
+		goto cleanup;
+	}
+	
+	gitg_runner_add_environment (runner, "GIT_INDEX_FILE", 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);
+	
+	g_file_delete (customindex, NULL, NULL);
+	g_object_unref (customindex);
+	
+	if (!writestash)
+	{
+		g_object_unref (customindex);
+		ret = FALSE;
+		showerror = TRUE;
 		
-		if (!gitg_repository_commandv (repository, NULL, "stash", NULL))
-		{
-			message_dialog (window,
-			                GTK_MESSAGE_ERROR,
-			                _("Could not stash changes from your current working copy."),
-			                NULL,
-			                NULL);
-			return FALSE;
-		}
+		goto cleanup;
+	}
+
+	gchar *stashtree = g_strndup (buffer->str, buffer->len);
+	gchar *reason = g_strdup_printf ("Automatic stash from gitg: ");
+
+	gitg_repository_run_command_with_inputv (repository, runner, reason, NULL,
+	                                         "commit-tree", stashtree,
+	                                         "-p", head,
+	                                         "-p", commit, NULL);
+	g_free (stashtree);
+	
+	if (buffer->len == 0)
+	{
+		g_free (reason);
+
+		ret = FALSE;
+		showerror = TRUE;
+		
+		goto cleanup;
+	}
+
+	gchar *rref = g_strndup (buffer->str, buffer->len);
+
+	if (ref)
+	{
+		*ref = g_strdup (rref);
 	}
 
-	if (output)
+	if (storeref)
 	{
-		g_strfreev (output);
+		// Make ref
+		gitg_repository_run_commandv (repository, runner, NULL,
+		                              "update-ref", "-m", reason, 
+		                              "refs/stash", rref, NULL);
 	}
 	
-	return TRUE;
+	g_free (rref);
+
+	gitg_repository_run_commandv (repository, runner, NULL,
+	                              "reset", "--hard", NULL);
+	ret = TRUE;
+
+cleanup:
+	g_string_free (buffer, TRUE);
+	g_object_unref (runner);
+	g_free (commit);
+	g_free (tree);
+	g_free (head);
+	
+	if (showerror)
+	{
+		message_dialog (window, 
+		                GTK_MESSAGE_ERROR,
+		                _("Failed to save current index state"),
+		                NULL,
+		                NULL);
+	}
+	
+	return ret;
+}
+
+static gboolean
+stash_changes (GitgWindow *window, gchar **ref, gboolean storeref)
+{
+	if (no_changes (gitg_window_get_repository (window)))
+	{
+		*ref = NULL;
+		return TRUE;
+	}
+
+	return stash_changes_real (window, ref, storeref);
+}
+
+static gboolean
+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;
+	}
 }
 
 static gboolean
 checkout_local_branch (GitgWindow *window,
                        GitgRef    *ref)
 {
-	if (!stash_changes (window, ref))
+	if (!stash_changes (window, NULL, TRUE))
 	{
 		return FALSE;
 	}
-		
-	GitgRepository *repository = gitg_window_get_repository (window);
+	
 	gchar const *name = gitg_ref_get_shortname (ref);
 	
-	if (!gitg_repository_commandv (repository, NULL, "checkout", name, NULL))
+	if (!checkout_local_branch_real (window, ref))
 	{
 		message_dialog (window,
 		                GTK_MESSAGE_ERROR,
@@ -434,7 +633,7 @@ checkout_local_branch (GitgWindow *window,
 	}
 	else
 	{
-		gitg_repository_load (repository, 1, (gchar const **)&name, NULL);
+		gitg_repository_load (gitg_window_get_repository (window), 1, (gchar const **)&name, NULL);
 		return TRUE;
 	}
 }
@@ -443,7 +642,7 @@ static gboolean
 checkout_remote_branch (GitgWindow *window,
                         GitgRef    *ref)
 {
-	if (!stash_changes (window, ref))
+	if (!stash_changes (window, NULL, TRUE))
 	{
 		return FALSE;
 	}
@@ -506,6 +705,122 @@ gitg_branch_actions_checkout (GitgWindow *window,
 	return ret;
 }
 
+typedef struct
+{
+	gboolean rebase;
+
+	GitgRef *source;
+	GitgRef *dest;
+	
+	gchar *stashcommit;
+	GitgRef *head;
+} RefInfo;
+
+static RefInfo *
+ref_info_new (GitgRef *source, GitgRef *dest)
+{
+	RefInfo *ret = g_slice_new0 (RefInfo);
+	
+	ret->source = gitg_ref_copy (source);
+	ret->dest = gitg_ref_copy (dest);
+	
+	return ret;
+}
+
+static void
+ref_info_free (RefInfo *info)
+{
+	gitg_ref_free (info->source);
+	gitg_ref_free (info->dest);
+
+	g_free (info->stashcommit);
+	gitg_ref_free (info->head);
+	
+	g_slice_free (RefInfo, info);
+}
+
+static void
+on_merge_rebase_result (GitgWindow   *window,
+                        GitgProgress  progress,
+                        gpointer      data)
+{
+	RefInfo *info = (RefInfo *)data;
+
+	if (progress == GITG_PROGRESS_ERROR)
+	{
+		gchar const *message;
+		
+		if (info->rebase)
+		{
+			message = _("Failed to rebase %s branch <%s> onto %s branch <%s>");
+		}
+		else
+		{
+			message = _("Failed to merge %s branch <%s> with %s branch <%s>");
+		}
+
+		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));
+	}
+	else if (progress == GITG_PROGRESS_SUCCESS)
+	{
+		GitgRepository *repository = gitg_window_get_repository (window);
+
+		// Checkout head
+		if (!checkout_local_branch_real (window, info->head))
+		{
+			gchar const *message = NULL;
+			
+			if (info->stashcommit)
+			{
+				gitg_repository_commandv (repository, NULL, 
+				                          "update-ref", "-m", "gitg autosave stash", 
+				                          "refs/stash", info->stashcommit, 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);
+		}
+		else if (info->stashcommit)
+		{
+			// Reapply stash
+			if (!gitg_repository_commandv (gitg_window_get_repository (window),
+			                               NULL,
+			                               "stash",
+			                               "apply",
+			                               "--index",
+			                               info->stashcommit,
+			                               NULL))
+			{
+				gitg_repository_commandv (repository, NULL, 
+				                          "update-ref", "-m", "gitg autosave stash", 
+				                          "refs/stash", info->stashcommit, NULL);
+
+				message_dialog (window,
+				                GTK_MESSAGE_ERROR,
+				                _("Failed to reapply stash correctly"),
+				                _("There might be unresolved conflicts in the working tree or index which you need to resolve manually"),
+				                NULL);
+			}
+		}
+
+		gitg_repository_reload (gitg_window_get_repository (window));
+	}
+
+	ref_info_free (info);
+}
+
 GitgRunner *
 gitg_branch_actions_merge (GitgWindow *window,
                            GitgRef    *source,
@@ -516,7 +831,71 @@ gitg_branch_actions_merge (GitgWindow *window,
 	g_return_val_if_fail (source != NULL, NULL);
 	g_return_val_if_fail (gitg_ref_get_ref_type (dest) != GITG_REF_TYPE_REMOTE, NULL);
 
-	return NULL;
+	gchar *message = g_strdup_printf (_("Are you sure you want to merge %s branch <%s> onto %s branch <%s>?"),
+	                                  gitg_ref_get_ref_type (source) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                                  gitg_ref_get_shortname (source),
+	                                  gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                                  gitg_ref_get_shortname (dest));
+
+	if (message_dialog (window,
+	                    GTK_MESSAGE_QUESTION,
+	                    _("Merge"),
+	                    message,
+	                    _("Merge")) != GTK_RESPONSE_ACCEPT)
+	{
+		g_free (message);
+		return NULL;
+	}
+	
+	g_free (message);
+	GitgRepository *repository = gitg_window_get_repository (window);
+	gchar *stashcommit = NULL;
+
+	if (!stash_changes (window, &stashcommit, FALSE))
+	{
+		return NULL;
+	}
+	
+	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))
+	{
+		g_free (stashcommit);
+		
+		message_dialog (window,
+		                GTK_MESSAGE_ERROR,
+		                _("Failed to checkout local branch <%s>"),
+		                _("The branch on which to merge could not be checked out"),
+		                NULL,
+		                gitg_ref_get_shortname (dest));
+		return NULL;
+	}
+
+	message = g_strdup_printf (_("Merging %s branch <%s> onto %s branch <%s>"),
+	                           gitg_ref_get_ref_type (source) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                           gitg_ref_get_shortname (source),
+	                           gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                           gitg_ref_get_shortname (dest));
+	
+	GitgRunner *ret;
+	RefInfo *info = ref_info_new (source, dest);
+	info->stashcommit = stashcommit;
+	info->head = gitg_ref_copy (head);
+	info->rebase = FALSE;
+
+	ret = run_progress (window, 
+	                    _("Merge"), 
+	                    message, 
+	                    on_merge_rebase_result,
+	                    info,
+	                    "merge",
+	                    gitg_ref_get_shortname (source),
+	                    NULL);
+	
+	g_free (message);
+	
+	return ret;
 }
 
 GitgRunner *
@@ -527,23 +906,89 @@ gitg_branch_actions_rebase (GitgWindow *window,
 	g_return_val_if_fail (GITG_IS_WINDOW (window), NULL);
 	g_return_val_if_fail (dest != NULL, NULL);
 	g_return_val_if_fail (source != NULL, NULL);
-	g_return_val_if_fail (gitg_ref_get_ref_type (dest) != GITG_REF_TYPE_REMOTE, NULL);
+	g_return_val_if_fail (gitg_ref_get_ref_type (source) != GITG_REF_TYPE_REMOTE, NULL);
 
-	return NULL;
-}
+	gchar *message = g_strdup_printf (_("Are you sure you want to rebase %s branch <%s> onto %s branch <%s>?"),
+	                                  gitg_ref_get_ref_type (source) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                                  gitg_ref_get_shortname (source),
+	                                  gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                                  gitg_ref_get_shortname (dest));
 
-typedef struct
-{
-	GitgRef *source;
-	GitgRef *dest;
-} PushInfo;
+	if (message_dialog (window,
+	                    GTK_MESSAGE_QUESTION,
+	                    _("Rebase"),
+	                    message,
+	                    _("Rebase")) != GTK_RESPONSE_ACCEPT)
+	{
+		g_free (message);
+		return NULL;
+	}
+	
+	g_free (message);
+	GitgRepository *repository = gitg_window_get_repository (window);
+	gchar *stashcommit = NULL;
+
+	if (!no_changes (repository))
+	{
+		// Check if destination is current HEAD
+		gchar *head = gitg_repository_parse_head (repository);
+		Hash hash;
+		
+		gitg_utils_sha1_to_hash (head, hash);
+		g_free (head);
+		
+		if (gitg_utils_hash_equal (hash, gitg_ref_get_hash (dest)))
+		{
+			message_dialog (window,
+			                GTK_MESSAGE_ERROR,
+			                _("Unable to rebase"),
+			                _("There are still uncommitted changes in your working tree and you are trying to rebase a branch onto the currently checked out branch. Either remove, stash or commit your changes first and try again"),
+			                NULL);
+			return NULL;
+		}
+
+		if (!stash_changes_real (window, &stashcommit, FALSE))
+		{
+			return NULL;
+		}
+	}
+
+	gchar *merge_head = gitg_utils_hash_to_sha1_new (gitg_ref_get_hash (dest));
+
+	message = g_strdup_printf (_("Rebasing %s branch <%s> onto %s branch <%s>"),
+	                           gitg_ref_get_ref_type (source) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                           gitg_ref_get_shortname (source),
+	                           gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH ? _("local") : _("remote"),
+	                           gitg_ref_get_shortname (dest));
+	
+	GitgRunner *ret;
+	RefInfo *info = ref_info_new (source, dest);
+	info->stashcommit = stashcommit;
+	info->head = gitg_ref_copy (gitg_repository_get_current_working_ref (repository));
+	info->rebase = TRUE;
+	
+	ret = run_progress (window, 
+	                    _("Rebase"), 
+	                    message, 
+	                    on_merge_rebase_result,
+	                    info,
+	                    "rebase",
+	                    merge_head,
+	                    gitg_ref_get_shortname (source),
+	                    NULL);
+	
+	g_free (message);
+	g_free (merge_head);
+	
+	return ret;
+}
 
 static void
 on_push_result (GitgWindow   *window,
                 GitgProgress  progress,
                 gpointer      data)
 {
-	PushInfo *info = (PushInfo *)data;
+	RefInfo *info = (RefInfo *)data;
 
 	if (progress == GITG_PROGRESS_ERROR)
 	{
@@ -560,9 +1005,7 @@ on_push_result (GitgWindow   *window,
 		gitg_repository_reload (gitg_window_get_repository (window));
 	}
 
-	gitg_ref_free (info->source);
-	gitg_ref_free (info->dest);
-	g_slice_free (PushInfo, info);
+	ref_info_free (info);
 }
 
 GitgRunner *
@@ -584,7 +1027,6 @@ gitg_branch_actions_push (GitgWindow *window,
 	                    GTK_MESSAGE_QUESTION,
 	                    _("Push"),
 	                    message,
-	                    NULL,
 	                    _("Push")) != GTK_RESPONSE_ACCEPT)
 	{
 		g_free (message);
@@ -598,14 +1040,12 @@ gitg_branch_actions_push (GitgWindow *window,
 	gchar const *name = gitg_ref_get_shortname (source);
 	
 	gchar *spec = g_strconcat (name, ":", local, NULL);
-	message = g_strdup_printf (_("Pushing local branch `%s' to remote branch `%s'"),
+	message = g_strdup_printf (_("Pushing local branch <%s> to remote branch <%s>"),
 	                           gitg_ref_get_shortname (source),
 	                           gitg_ref_get_shortname (dest));
 	
 	GitgRunner *ret;
-	PushInfo *info = g_slice_new (PushInfo);
-	info->source = gitg_ref_copy (source);
-	info->dest = gitg_ref_copy (dest);
+	RefInfo *info = ref_info_new (source, dest);
 	
 	ret = run_progress (window, 
 	                    _("Push"), 
@@ -624,11 +1064,73 @@ gitg_branch_actions_push (GitgWindow *window,
 	return ret;
 }
 
+GitgRunner *
+gitg_branch_actions_push_remote (GitgWindow  *window,
+                                 GitgRef     *source,
+                                 gchar const *remote)
+{
+	g_return_val_if_fail (GITG_IS_WINDOW (window), NULL);
+	g_return_val_if_fail (remote != NULL, NULL);
+	g_return_val_if_fail (source != NULL, NULL);	
+	g_return_val_if_fail (gitg_ref_get_ref_type (source) == GITG_REF_TYPE_BRANCH, NULL);
+	
+	gchar *message = g_strdup_printf (_("Are you sure you want to push <%s> to remote <%s>?"),
+	                                  gitg_ref_get_shortname (source),
+	                                  remote);
+	
+	if (message_dialog (window,
+	                    GTK_MESSAGE_QUESTION,
+	                    _("Push"),
+	                    message,
+	                    _("Push")) != GTK_RESPONSE_ACCEPT)
+	{
+		g_free (message);
+		return NULL;
+	}
+	
+	g_free (message);
+
+	gchar const *name = gitg_ref_get_shortname (source);
+	gchar *rm = g_strconcat (remote, "/", name, NULL);
+	
+	gchar *spec = g_strconcat (name, ":", name, NULL);
+	message = g_strdup_printf (_("Pushing local branch <%s> to remote branch <%s>"),
+	                           gitg_ref_get_shortname (source),
+	                           rm);
+	
+	g_free (rm);
+
+	GitgRunner *ret;
+	gchar *rr = g_strconcat ("refs/remotes/", remote, "/", name, NULL);
+	GitgRef *rmref = gitg_ref_new ("0000000000000000000000000000000000000000", remote);
+	g_free (rr);
+
+	RefInfo *info = ref_info_new (source, rmref);
+	gitg_ref_free (rmref);
+	
+	ret = run_progress (window, 
+	                    _("Push"), 
+	                    message, 
+	                    on_push_result,  
+	                    info,
+	                    "push",
+	                    remote,
+	                    spec,
+	                    NULL);
+	
+	g_free (message);
+	g_free (spec);
+	
+	return ret;
+}
+
 gboolean
 gitg_branch_actions_apply_stash (GitgWindow *window,
                                  GitgRef    *stash)
 {
 	g_return_val_if_fail (GITG_IS_WINDOW (window), FALSE);
+	g_return_val_if_fail (stash != NULL, FALSE);	
+	
 	return FALSE;
 }
 
diff --git a/gitg/gitg-branch-actions.h b/gitg/gitg-branch-actions.h
index 3acb59a..4cc8cdb 100644
--- a/gitg/gitg-branch-actions.h
+++ b/gitg/gitg-branch-actions.h
@@ -35,6 +35,7 @@ GitgRunner *gitg_branch_actions_merge (GitgWindow *window, GitgRef *source, Gitg
 GitgRunner *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);
 
 G_END_DECLS
 
diff --git a/gitg/gitg-menus.xml b/gitg/gitg-menus.xml
index d1fbb59..675d905 100644
--- a/gitg/gitg-menus.xml
+++ b/gitg/gitg-menus.xml
@@ -49,13 +49,18 @@
           </object>
         </child>
         <child>
+          <object class="GtkAction" id="RebaseAction">
+            <property name="label">Rebase branch onto...</property>
+          </object>
+        </child>
+        <child>
           <object class="GtkAction" id="MergeAction">
-            <property name="label">Merge branch onto...</property>
+            <property name="label">Merge branch with...</property>
           </object>
         </child>
         <child>
-          <object class="GtkAction" id="RebaseAction">
-            <property name="label">Rebase branch onto...</property>
+          <object class="GtkAction" id="PushAction">
+            <property name="label">Push branch to...</property>
           </object>
         </child>
       </object>
@@ -65,11 +70,13 @@
         <child>
           <object class="GtkAction" id="MergeDndAction">
             <property name="label">Merge</property>
+            <signal name="activate" handler="on_merge_branch_action_activate"/>
           </object>
         </child>
         <child>
           <object class="GtkAction" id="RebaseDndAction">
             <property name="label">Rebase</property>
+            <signal name="activate" handler="on_rebase_branch_action_activate"/>
           </object>
         </child>
       </object>
@@ -88,10 +95,12 @@
         <menu name="Rebase" action="RebaseAction">
           <placeholder name="Placeholder"/>
         </menu>
-        <separator/>
         <menu name="Merge" action="MergeAction">
           <placeholder name="Placeholder"/>
         </menu>
+        <menu name="Push" action="PushAction">
+          <placeholder name="Placeholder"/>
+        </menu>
       </popup>
       <popup name="dnd_popup">
         <menuitem action="RebaseDndAction"/>
diff --git a/gitg/gitg-ref.c b/gitg/gitg-ref.c
index f577338..b620170 100644
--- a/gitg/gitg-ref.c
+++ b/gitg/gitg-ref.c
@@ -105,7 +105,12 @@ gitg_ref_new(gchar const *hash, gchar const *name)
 GitgRef *
 gitg_ref_copy(GitgRef *ref)
 {
-	GitgRef *ret = g_slice_new0(GitgRef);
+	if (ref == NULL)
+	{
+		return NULL;
+	}
+
+	GitgRef *ret = g_slice_new0 (GitgRef);
 	
 	ret->type = ref->type;
 	ret->name = g_strdup(ref->name);
diff --git a/gitg/gitg-repository-dialog.c b/gitg/gitg-repository-dialog.c
index 5eaa78d..c1bfe3c 100644
--- a/gitg/gitg-repository-dialog.c
+++ b/gitg/gitg-repository-dialog.c
@@ -316,6 +316,7 @@ init_remotes(GitgRepositoryDialog *dialog)
 	
 	if (!ret)
 	{
+		update_sensitivity (dialog);
 		return;
 	}
 	
diff --git a/gitg/gitg-repository.c b/gitg/gitg-repository.c
index e3aeac5..91a9e7a 100644
--- a/gitg/gitg-repository.c
+++ b/gitg/gitg-repository.c
@@ -27,6 +27,7 @@
 #include "gitg-types.h"
 #include "gitg-preferences.h"
 #include "gitg-data-binding.h"
+#include "gitg-config.h"
 
 #include <gio/gio.h>
 #include <glib/gi18n.h>
@@ -1427,3 +1428,47 @@ gitg_repository_get_current_working_ref(GitgRepository *repository)
 	
 	return repository->priv->working_ref;
 }
+
+gchar **
+gitg_repository_get_remotes (GitgRepository *repository)
+{
+	g_return_val_if_fail (GITG_IS_REPOSITORY (repository), NULL);
+
+	GitgConfig *config = gitg_config_new (repository);
+	gchar *ret = gitg_config_get_value_regex (config, "remote\\..*\\.url");
+
+	gchar **remotes = g_malloc (sizeof (gchar *));
+	remotes[0] = NULL;
+	
+	if (!ret)
+	{
+		g_object_unref (config);
+		return remotes;
+	}
+	
+	gchar **lines = g_strsplit(ret, "\n", -1);
+	gchar **ptr = lines;
+	
+	GRegex *regex = g_regex_new ("remote\\.(.+?)\\.url\\s+(.*)", 0, 0, NULL);
+	gint num = 0;
+	
+	while (*ptr)
+	{
+		GMatchInfo *info = NULL;
+		
+		if (g_regex_match (regex, *ptr, 0, &info))
+		{
+			gchar *name = g_match_info_fetch (info, 1);
+			
+			remotes = g_realloc (ret, sizeof(gchar *) * (++num + 1));
+			remotes[num - 1] = name;
+		}
+		
+		g_match_info_free (info);
+		++ptr;
+	}
+	
+	remotes[num] = NULL;
+	g_object_unref (config);
+	return remotes;
+}
diff --git a/gitg/gitg-repository.h b/gitg/gitg-repository.h
index d0c78f5..6060b0e 100644
--- a/gitg/gitg-repository.h
+++ b/gitg/gitg-repository.h
@@ -107,6 +107,8 @@ gchar *gitg_repository_parse_head(GitgRepository *repository);
 
 void gitg_repository_reload(GitgRepository *repository);
 
+gchar **gitg_repository_get_remotes (GitgRepository *repository);
+
 G_END_DECLS
 
 #endif /* __GITG_REPOSITORY_H__ */
diff --git a/gitg/gitg-runner.c b/gitg/gitg-runner.c
index 1fdd3d1..73ae594 100644
--- a/gitg/gitg-runner.c
+++ b/gitg/gitg-runner.c
@@ -66,6 +66,7 @@ struct _GitgRunnerPrivate
 	gchar *buffer;
 	gchar *read_buffer;
 	gchar **lines;
+	gchar **environment;
 	
 	gint exit_status;
 };
@@ -119,11 +120,6 @@ runner_io_exit(GPid pid, gint status, GitgRunner *runner)
 }
 
 static void
-dummy_exit(GPid pid, gint status, gpointer data)
-{
-}
-
-static void
 free_lines(GitgRunner *runner)
 {
 	gint i = 0;
@@ -151,6 +147,7 @@ gitg_runner_finalize(GObject *object)
 	
 	/* Remove line buffer */
 	g_free(runner->priv->buffer);
+	g_strfreev (runner->priv->environment);
 	
 	g_object_unref(runner->priv->cancellable);
 
@@ -424,7 +421,7 @@ run_sync(GitgRunner *runner, gchar const *input, GError **error)
 	if (status != 0 && error)
 		g_set_error(error, GITG_RUNNER_ERROR, GITG_RUNNER_ERROR_EXIT, "Did not exit without error code");
 	
-	return status == 0;
+	return status == EXIT_SUCCESS;
 }
 
 static void
@@ -642,12 +639,15 @@ gitg_runner_cancel(GitgRunner *runner)
 		
 		runner->priv->cancellable = g_cancellable_new();
 
-		g_child_watch_add(runner->priv->pid, dummy_cb, NULL);
-		kill(runner->priv->pid, SIGTERM);
+		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);
+		}
 		
-		runner_io_exit(runner->priv->pid, EXIT_FAILURE, runner);
 		close_streams(runner);
-
 		g_signal_emit(runner, runner_signals[END_LOADING], 0, TRUE);
 	}
 }
@@ -666,3 +666,42 @@ gitg_runner_get_exit_status(GitgRunner *runner)
 	
 	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);
+	gint len = g_strv_length ((gchar **)environment);
+	
+	runner->priv->environment = g_new (gchar *, len + 1);
+	gint i;
+
+	for (i = 0; i < len; ++i)
+	{
+		runner->priv->environment[i] = g_strdup (environment[i]);
+	}
+	
+	runner->priv->environment[len] = NULL;
+}
+
+void
+gitg_runner_add_environment (GitgRunner *runner, gchar const *key, gchar const *value)
+{
+	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)
+	{
+		runner->priv->environment = g_listenv ();
+	}
+	
+	gint len = g_strv_length (runner->priv->environment);
+	runner->priv->environment = g_realloc (runner->priv->environment, 
+	                                       sizeof(gchar *) * (len + 2));
+
+	runner->priv->environment[len] = g_strconcat (key, "=", value, NULL);
+	runner->priv->environment[len + 1] = NULL;
+}
diff --git a/gitg/gitg-runner.h b/gitg/gitg-runner.h
index d841089..2d848fa 100644
--- a/gitg/gitg-runner.h
+++ b/gitg/gitg-runner.h
@@ -79,6 +79,9 @@ 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);
+
 GQuark gitg_runner_error_quark();
 
 G_END_DECLS
diff --git a/gitg/gitg-spinner.c b/gitg/gitg-spinner.c
index a5f327b..d8d56ec 100644
--- a/gitg/gitg-spinner.c
+++ b/gitg/gitg-spinner.c
@@ -729,7 +729,7 @@ gitg_spinner_set_screen (GitgSpinner *spinner, GdkScreen *screen)
 static void
 gitg_spinner_dispose (GObject *object)
 {
-	GitgSpinner *spinner = GITG_SPINNER (object);
+	//GitgSpinner *spinner = GITG_SPINNER (object);
 
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
diff --git a/gitg/gitg-window.c b/gitg/gitg-window.c
index d32223c..6d42003 100644
--- a/gitg/gitg-window.c
+++ b/gitg/gitg-window.c
@@ -43,6 +43,8 @@
 #include "gitg-dnd.h"
 #include "gitg-branch-actions.h"
 
+#define DYNAMIC_ACTION_DATA_KEY "GitgDynamicActionDataKey"
+
 #define GITG_WINDOW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_WINDOW, GitgWindowPrivate))
 
 enum
@@ -85,7 +87,7 @@ struct _GitgWindowPrivate
 	gboolean destroy_has_run;
 	guint merge_rebase_uid;
 	GtkActionGroup *merge_rebase_action_group;
-	GitgRef *popup_ref;
+	GitgRef *popup_refs[2];
 	
 	GList *branch_actions;
 };
@@ -482,7 +484,7 @@ update_dnd_status (GitgWindow *window, GitgRef *source, GitgRef *dest)
 		else if (gitg_ref_get_ref_type (source) == GITG_REF_TYPE_REMOTE &&
 		         gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH)
 		{
-			message = g_strdup_printf (_("Merge/rebase remote branch <%s> with/on local branch <%s>"), gitg_ref_get_shortname (source), gitg_ref_get_shortname (dest));
+			message = g_strdup_printf (_("Merge/rebase local branch <%s> with/on remote branch <%s>"), gitg_ref_get_shortname (dest), gitg_ref_get_shortname (source));
 		}
 		
 		if (message)
@@ -510,6 +512,16 @@ on_refs_dnd (GitgRef *source, GitgRef *dest, gboolean dropped, GitgWindow *windo
 	{
 		ret = add_branch_action (window, gitg_branch_actions_push (window, source, dest));
 	}
+	else if (gitg_ref_get_ref_type (dest) == GITG_REF_TYPE_BRANCH)
+	{
+		GtkWidget *popup = gtk_ui_manager_get_widget (window->priv->menus_ui_manager,
+	                                                  "/ui/dnd_popup");
+
+		window->priv->popup_refs[0] = source;
+		window->priv->popup_refs[1] = dest;
+
+		gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL, 1, gtk_get_current_event_time());
+	}
 
 	gtk_statusbar_push (window->priv->statusbar, 0, "");
 	return ret;
@@ -552,7 +564,8 @@ gitg_window_parser_finished(GtkBuildable *buildable, GtkBuilder *builder)
 
 	gtk_box_pack_start(GTK_BOX(vbox), menu, FALSE, FALSE, 0);
 	gtk_box_reorder_child(GTK_BOX(vbox), menu, 0);
-
+	
+	gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (uiman));
 
 	window->priv->edit_group = GTK_ACTION_GROUP(gtk_builder_get_object(b, "action_group_menu_edit"));
 
@@ -1364,6 +1377,37 @@ on_repository_properties(GtkAction *action, GitgWindow *window)
 }
 
 static void
+on_push_activated (GtkAction *action, GitgWindow *window)
+{
+	gchar const *ptr = g_object_get_data (G_OBJECT (action), 
+	                                      DYNAMIC_ACTION_DATA_KEY);
+	add_branch_action (window,
+	                   gitg_branch_actions_push_remote (window, window->priv->popup_refs[0], ptr));
+}
+
+static void
+on_rebase_activated (GtkAction *action, GitgWindow *window)
+{
+	GitgRef *dest = g_object_get_data (G_OBJECT (action), 
+	                                   DYNAMIC_ACTION_DATA_KEY);
+
+	add_branch_action (window, gitg_branch_actions_rebase (window, 
+	                                                       window->priv->popup_refs[0],
+	                                                       dest));
+}
+
+static void
+on_merge_activated (GtkAction *action, GitgWindow *window)
+{
+	GitgRef *dest = g_object_get_data (G_OBJECT (action), 
+	                                   DYNAMIC_ACTION_DATA_KEY);
+
+	add_branch_action (window, gitg_branch_actions_merge (window, 
+	                                                      dest,
+	                                                      window->priv->popup_refs[0]));
+}
+
+static void
 update_merge_rebase (GitgWindow *window, GitgRef *ref)
 {
 	if (window->priv->merge_rebase_uid != 0)
@@ -1415,16 +1459,36 @@ update_merge_rebase (GitgWindow *window, GitgRef *ref)
 		
 		if (gitg_ref_get_ref_type (r) == GITG_REF_TYPE_BRANCH && !gitg_ref_equal (r, ref))
 		{
-			gchar *rebase = g_strdup_printf ("Rebase%sAction", gitg_ref_get_shortname (r));
-			gchar *merge = g_strdup_printf ("Merge%sAction", gitg_ref_get_shortname (r));
+			gchar const *rname = gitg_ref_get_shortname (r);
 			
-			GtkAction *rebaseac = gtk_action_new (rebase, gitg_ref_get_shortname (r), NULL, NULL);
-			GtkAction *mergeac = gtk_action_new (merge, gitg_ref_get_shortname (r), NULL, NULL);
+			gchar *rebase = g_strconcat ("Rebase", rname, "Action", NULL);
+			gchar *merge = g_strconcat ("Merge", rname, "Action", NULL);
+			
+			GtkAction *rebaseac = gtk_action_new (rebase, rname, NULL, NULL);
+			GtkAction *mergeac = gtk_action_new (merge, rname, NULL, NULL);
+
+			g_object_set_data_full (G_OBJECT (rebaseac),
+			                        DYNAMIC_ACTION_DATA_KEY,
+			                        gitg_ref_copy (r),
+			                        (GDestroyNotify)gitg_ref_free);
+			g_object_set_data_full (G_OBJECT (mergeac),
+			                        DYNAMIC_ACTION_DATA_KEY,
+			                        gitg_ref_copy (r),
+			                        (GDestroyNotify)gitg_ref_free);
+
+			g_signal_connect (rebaseac,
+			                  "activate",
+			                  G_CALLBACK (on_rebase_activated),
+			                  window);
+			g_signal_connect (mergeac,
+			                  "activate",
+			                  G_CALLBACK (on_merge_activated),
+			                  window);
 
 			gtk_action_group_add_action (ac, rebaseac);
 			gtk_action_group_add_action (ac, mergeac);
 			
-			gchar *name = g_strconcat ("Rebase", gitg_ref_get_shortname (r), NULL);
+			gchar *name = g_strconcat ("Rebase", rname, NULL);
 
 			gtk_ui_manager_add_ui (window->priv->menus_ui_manager,
 			                       window->priv->merge_rebase_uid,
@@ -1435,7 +1499,7 @@ update_merge_rebase (GitgWindow *window, GitgRef *ref)
 			                       FALSE);
 			g_free (name);
 			
-			name = g_strconcat ("Merge", gitg_ref_get_shortname (r), NULL);
+			name = g_strconcat ("Merge", rname, NULL);
 
 			gtk_ui_manager_add_ui (window->priv->menus_ui_manager,
 			                       window->priv->merge_rebase_uid,
@@ -1453,6 +1517,39 @@ update_merge_rebase (GitgWindow *window, GitgRef *ref)
 	
 	g_slist_foreach (refs, (GFunc)gitg_ref_free, NULL);
 	g_slist_free (refs);
+	
+	gchar **remotes = gitg_repository_get_remotes (window->priv->repository);
+	gchar **ptr = remotes;
+	
+	while (*ptr)
+	{
+		gchar *push = g_strconcat ("Push", *ptr, "Action", NULL);
+		GtkAction *pushac = gtk_action_new (push, *ptr, NULL, NULL);
+		
+		gtk_action_group_add_action (ac, pushac);
+		
+		gchar *name = g_strconcat ("Push", *ptr, NULL);
+		gtk_ui_manager_add_ui (window->priv->menus_ui_manager,
+			                       window->priv->merge_rebase_uid,
+			                       "/ui/revision_popup/Push/Placeholder",
+			                       name,
+			                       push,
+			                       GTK_UI_MANAGER_MENUITEM,
+			                       FALSE);
+
+		g_object_set_data_full (G_OBJECT (pushac),
+		                        DYNAMIC_ACTION_DATA_KEY,
+		                        g_strdup (*ptr),
+		                        (GDestroyNotify)g_free);
+
+		g_signal_connect (pushac, 
+		                  "activate", 
+		                  G_CALLBACK (on_push_activated), 
+		                  window);
+		++ptr;
+	}
+	
+	g_strfreev (remotes);	
 
 	gtk_ui_manager_ensure_update (window->priv->menus_ui_manager);
 }
@@ -1566,7 +1663,7 @@ popup_revision (GitgWindow *window, GdkEventButton *event)
 	}
 	
 	update_merge_rebase (window, ref);
-	window->priv->popup_ref = ref;
+	window->priv->popup_refs[0] = ref;
 
 	gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, window, event->button, event->time);
 	return TRUE;
@@ -1587,12 +1684,50 @@ on_tree_view_rv_button_press_event (GtkWidget *widget, GdkEventButton *event, Gi
 void
 on_checkout_branch_action_activate (GtkAction *action, GitgWindow *window)
 {
-	gitg_branch_actions_checkout (window, window->priv->popup_ref);
+	gitg_branch_actions_checkout (window, window->priv->popup_refs[0]);
 }
 
-
 void
 on_remove_branch_action_activate (GtkAction *action, GitgWindow *window)
 {
-	gitg_branch_actions_remove (window, window->priv->popup_ref);
+	gitg_branch_actions_remove (window, window->priv->popup_refs[0]);
 }
+
+void
+on_rebase_branch_action_activate (GtkAction *action, GitgWindow *window)
+{
+	gint source;
+	
+	if (gitg_ref_get_ref_type (window->priv->popup_refs[0]) == GITG_REF_TYPE_REMOTE)
+	{
+		source = 1;
+	}
+	else
+	{
+		source = 0;
+	}
+	
+	add_branch_action (window, gitg_branch_actions_rebase (window, 
+	                                                       window->priv->popup_refs[source],
+	                                                       window->priv->popup_refs[!source]));
+}
+
+void
+on_merge_branch_action_activate (GtkAction *action, GitgWindow *window)
+{
+	gint source;
+	
+	if (gitg_ref_get_ref_type (window->priv->popup_refs[0]) == GITG_REF_TYPE_REMOTE)
+	{
+		source = 1;
+	}
+	else
+	{
+		source = 0;
+	}
+
+	add_branch_action (window, gitg_branch_actions_merge (window, 
+	                                                       window->priv->popup_refs[!source],
+	                                                       window->priv->popup_refs[source]));
+}
+



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