[gitg] Implement cherry picking of single commits



commit 881cb12a0130c25940c282d11f8e12d8d32d779a
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Thu Aug 20 00:47:29 2015 +0200

    Implement cherry picking of single commits

 gitg/Makefile.am                         |    1 +
 gitg/gitg-action-support.vala            |  105 +++++++-
 gitg/gitg-commit-action-cherry-pick.vala |  282 ++++++++++++++++++
 gitg/gitg-ref-action-merge.vala          |   81 +-----
 gitg/history/gitg-history.vala           |    5 +
 tests/gitg/Makefile.am                   |    2 +
 tests/gitg/main.vala                     |    3 +-
 tests/gitg/test-cherry-pick-commit.vala  |  476 ++++++++++++++++++++++++++++++
 tests/gitg/test-merge-ref.vala           |    4 +-
 9 files changed, 882 insertions(+), 77 deletions(-)
---
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index 5cdbaa1..52516ca 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -63,6 +63,7 @@ gitg_gitg_VALASOURCES =                                               \
        gitg/gitg-commit-action-create-branch.vala              \
        gitg/gitg-commit-action-create-patch.vala               \
        gitg/gitg-commit-action-create-tag.vala                 \
+       gitg/gitg-commit-action-cherry-pick.vala                \
        gitg/gitg-create-branch-dialog.vala                     \
        gitg/gitg-create-tag-dialog.vala                        \
        gitg/gitg-dash-view.vala                                \
diff --git a/gitg/gitg-action-support.vala b/gitg/gitg-action-support.vala
index 50ac09b..6eb7ad8 100644
--- a/gitg/gitg-action-support.vala
+++ b/gitg/gitg-action-support.vala
@@ -35,7 +35,9 @@ public class ActionSupport : Object
 
        public async bool working_directory_dirty()
        {
-               var options = new Ggit.StatusOptions(0, Ggit.StatusShow.WORKDIR_ONLY, null);
+               var options = new Ggit.StatusOptions(Ggit.StatusOption.EXCLUDE_SUBMODULES,
+                                                    Ggit.StatusShow.WORKDIR_ONLY,
+                                                    null);
                var is_dirty = false;
 
                yield Async.thread_try(() => {
@@ -133,7 +135,7 @@ public class ActionSupport : Object
 
                        if ((yield application.user_query_async(q)) != Gtk.ResponseType.OK)
                        {
-                               notification.error(_("Merge failed with conflicts"));
+                               notification.error(_("Failed with conflicts"));
                                return false;
                        }
 
@@ -146,7 +148,7 @@ public class ActionSupport : Object
                return true;
        }
 
-       public async bool checkout_conflicts(SimpleNotification notification, Gitg.Ref reference, Ggit.Index 
index, Gitg.Ref source, Gitg.Ref? head)
+       public async bool checkout_conflicts(SimpleNotification notification, Gitg.Ref reference, Ggit.Index 
index, Gitg.Ref? head)
        {
                if (!(yield stash_if_needed(notification, head)))
                {
@@ -182,6 +184,103 @@ public class ActionSupport : Object
 
                return true;
        }
+
+       public async Ggit.OId? commit_index(SimpleNotification notification,
+                                           Gitg.Ref           reference,
+                                           Ggit.Index         index,
+                                           owned Ggit.OId[]?  parents,
+                                           Ggit.Signature?    author,
+                                           string             message)
+       {
+               var committer = application.get_verified_committer();
+
+               if (committer == null)
+               {
+                       notification.error(_("Failed to obtain author details"));
+                       return null;
+               }
+
+               if (author == null)
+               {
+                       author = committer;
+               }
+
+               var stage = application.repository.stage;
+
+               Gitg.Ref? head = null;
+               var ishead = reference_is_head(reference, ref head);
+
+               Ggit.OId? oid = null;
+               Ggit.Tree? head_tree = null;
+               Gitg.Commit? commit = null;
+
+               try
+               {
+                       commit = reference.lookup() as Gitg.Commit;
+               }
+               catch (Error e)
+               {
+                       notification.error(_("Failed to lookup commit: %s").printf(e.message));
+                       return null;
+               }
+
+               if (ishead)
+               {
+                       if (!(yield stash_if_needed(notification, head)))
+                       {
+                               return null;
+                       }
+
+                       head_tree = commit.get_tree();
+               }
+
+               if (parents == null)
+               {
+                       parents = new Ggit.OId[] { commit.get_id() };
+               }
+
+               try
+               {
+                       // TODO: not all hooks are being executed yet
+                       oid = yield stage.commit_index(index,
+                                                      ishead ? head : reference,
+                                                      message,
+                                                      author,
+                                                      committer,
+                                                      parents,
+                                                      StageCommitOptions.NONE);
+               }
+               catch (Error e)
+               {
+                       notification.error(_("Failed to create commit: %s").printf(e.message));
+                       return null;
+               }
+
+               if (ishead)
+               {
+                       try
+                       {
+                               yield Async.thread(() => {
+                                       var opts = new Ggit.CheckoutOptions();
+
+                                       opts.set_strategy(Ggit.CheckoutStrategy.SAFE);
+                                       opts.set_baseline(head_tree);
+
+                                       var newcommit = application.repository.lookup<Ggit.Commit>(oid);
+                                       var newtree = newcommit.get_tree();
+
+                                       application.repository.checkout_tree(newtree, opts);
+                               });
+                       }
+                       catch (Error e)
+                       {
+                               notification.error(_("Failed to checkout index: %s").printf(e.message));
+                               return null;
+                       }
+               }
+
+               return oid;
+       }
 }
 
 }
diff --git a/gitg/gitg-commit-action-cherry-pick.vala b/gitg/gitg-commit-action-cherry-pick.vala
new file mode 100644
index 0000000..0065d43
--- /dev/null
+++ b/gitg/gitg-commit-action-cherry-pick.vala
@@ -0,0 +1,282 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2015 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Gitg
+{
+
+class CommitActionCherryPick : GitgExt.UIElement, GitgExt.Action, GitgExt.CommitAction, Object
+{
+       // Do this to pull in config.h before glib.h (for gettext...)
+       private const string version = Gitg.Config.VERSION;
+
+       public GitgExt.Application? application { owned get; construct set; }
+       public GitgExt.RefActionInterface action_interface { get; construct set; }
+       public Gitg.Commit commit { get; construct set; }
+
+       private Gitg.Ref[]? d_destinations;
+       private ActionSupport d_support;
+
+       public CommitActionCherryPick(GitgExt.Application        application,
+                                     GitgExt.RefActionInterface action_interface,
+                                     Gitg.Commit                commit)
+       {
+               Object(application:      application,
+                      action_interface: action_interface,
+                      commit:           commit);
+
+               d_support = new ActionSupport(application, action_interface);
+       }
+
+       public string id
+       {
+               owned get { return "/org/gnome/gitg/commit-actions/cherry-pick"; }
+       }
+
+       public string display_name
+       {
+               owned get { return _("Cherry pick onto"); }
+       }
+
+       public string description
+       {
+               owned get { return _("Cherry pick this commit onto a branch"); }
+       }
+
+       public bool available
+       {
+               get { return true; }
+       }
+
+       public bool enabled
+       {
+               get
+               {
+                       if (commit.get_parents().get_size() > 1)
+                       {
+                               return false;
+                       }
+
+                       ensure_destinations();
+                       return d_destinations.length != 0;
+               }
+       }
+
+       private void ensure_destinations()
+       {
+               if (d_destinations != null)
+               {
+                       return;
+               }
+
+               d_destinations = new Gitg.Ref[0];
+
+               foreach (var r in action_interface.references)
+               {
+                       if (r.is_branch())
+                       {
+                               try
+                               {
+                                       var c = r.lookup() as Ggit.Commit;
+
+                                       if (!c.get_id().equal(commit.get_id()))
+                                       {
+                                               d_destinations += r;
+                                       }
+                               } catch {}
+                       }
+               }
+       }
+
+       private async Ggit.Index? create_index(SimpleNotification notification, Gitg.Ref destination)
+       {
+               Gitg.Commit? theirs = null;
+               string theirs_name = destination.parsed_name.shortname;
+
+               try
+               {
+                       theirs = destination.lookup() as Gitg.Commit;
+               }
+               catch (Error e)
+               {
+                       notification.error(_("Failed to lookup the commit for branch %s: 
%s").printf(@"'$theirs_name'", e.message));
+                       return null;
+               }
+
+               var merge_options = new Ggit.MergeOptions();
+               Ggit.Index? index = null;
+
+               try
+               {
+                       yield Async.thread(() => {
+                               index = application.repository.cherry_pick_commit(commit, theirs, 0, 
merge_options);
+                       });
+               }
+               catch (Error e)
+               {
+                       notification.error(_("Failed to cherry-pick the commit: %s").printf(e.message));
+                       return null;
+               }
+
+               return index;
+       }
+
+       private async bool checkout_conflicts(SimpleNotification notification, Ggit.Index index, Gitg.Ref 
destination)
+       {
+               var ours_name = commit.get_id().to_string()[0:6];
+               var theirs_name = destination.parsed_name.shortname;
+
+               notification.message = _("Cherry pick has conflicts");
+
+               Gitg.Ref? head = null;
+               var ishead = d_support.reference_is_head(destination, ref head);
+
+               string message;
+
+               if (ishead)
+               {
+                       message = _("The cherry pick of %s onto %s has caused conflicts, would you like to 
checkout branch %s with the cherry pick to your working directory to resolve the 
conflicts?").printf(@"'$ours_name'", @"'$theirs_name'", @"'$theirs_name'");
+               }
+               else
+               {
+                       message = _("The cherry-pick of %s onto %s has caused conflicts, would you like to 
checkout the cherry pick to your working directory to resolve the conflicts?").printf(@"'$ours_name'", 
@"'$theirs_name'");
+               }
+
+               var q = new GitgExt.UserQuery.full(_("Cherry pick has conflicts"),
+                                                  message,
+                                                  Gtk.MessageType.QUESTION,
+                                                  _("Cancel"), Gtk.ResponseType.CANCEL,
+                                                  _("Checkout"), Gtk.ResponseType.OK);
+
+               if ((yield application.user_query_async(q)) != Gtk.ResponseType.OK)
+               {
+                       notification.error(_("Cherry pick failed with conflicts"));
+                       return false;
+               }
+
+               if (!(yield d_support.checkout_conflicts(notification, destination, index, head)))
+               {
+                       return false;
+               }
+
+               write_cherry_pick_state_files();
+
+               notification.success(_("Cherry pick finished with conflicts in working directory"));
+               return true;
+       }
+
+       private void write_cherry_pick_state_files()
+       {
+               var wd = application.repository.get_location().get_path();
+
+               try
+               {
+                       FileUtils.set_contents(Path.build_filename(wd, "CHERRY_PICK_HEAD"), 
"%s\n".printf(commit.get_id().to_string()));
+               } catch {}
+       }
+
+       public async void cherry_pick(Gitg.Ref destination)
+       {
+               var id = commit.get_id();
+               var shortid = id.to_string()[0:6];
+               var name = destination.parsed_name.shortname;
+
+               var notification = new SimpleNotification(_("Cherry pick %s onto %s").printf(@"'$shortid'", 
@"'$name'"));
+
+               application.notifications.add(notification);
+
+               var index = yield create_index(notification, destination);
+
+               if (index == null)
+               {
+                       return;
+               }
+
+               if (index.has_conflicts())
+               {
+                       yield checkout_conflicts(notification, index, destination);
+                       return;
+               }
+
+               var oid = yield d_support.commit_index(notification,
+                                                      destination,
+                                                      index,
+                                                      null,
+                                                      commit.get_author(),
+                                                      commit.get_message());
+
+               if (oid != null) {
+                       notification.success(_("Successfully cherry picked"));
+               }
+       }
+
+       private void activate_destination(Gitg.Ref destination)
+       {
+               cherry_pick.begin(destination, (obj, res) => {
+                       cherry_pick.end(res);
+               });
+       }
+
+       public void populate_menu(Gtk.Menu menu)
+       {
+               if (!available)
+               {
+                       return;
+               }
+
+               ensure_destinations();
+
+               if (!enabled)
+               {
+                       return;
+               }
+
+               var item = new Gtk.MenuItem.with_label(display_name);
+               item.tooltip_text = description;
+               item.show();
+
+               var submenu = new Gtk.Menu();
+               submenu.show();
+
+               foreach (var dest in d_destinations)
+               {
+                       var name = dest.parsed_name.shortname;
+                       var subitem = new Gtk.MenuItem.with_label(name);
+
+                       subitem.tooltip_text = _("Cherry pick onto %s").printf(@"'$name'");
+                       subitem.show();
+
+                       subitem.activate.connect(() => {
+                               activate_destination(dest);
+                       });
+
+                       submenu.append(subitem);
+               }
+
+               item.submenu = submenu;
+               menu.append(item);
+       }
+
+       public void activate()
+       {
+       }
+}
+
+}
+
+// ex:set ts=4 noet
diff --git a/gitg/gitg-ref-action-merge.vala b/gitg/gitg-ref-action-merge.vala
index a368294..7815c2b 100644
--- a/gitg/gitg-ref-action-merge.vala
+++ b/gitg/gitg-ref-action-merge.vala
@@ -200,7 +200,7 @@ class RefActionMerge : GitgExt.UIElement, GitgExt.Action, GitgExt.RefAction, Obj
                        return false;
                }
 
-               if (!(yield d_support.checkout_conflicts(notification, reference, index, source, head)))
+               if (!(yield d_support.checkout_conflicts(notification, reference, index, head)))
                {
                        return false;
                }
@@ -255,14 +255,6 @@ class RefActionMerge : GitgExt.UIElement, GitgExt.Action, GitgExt.RefAction, Obj
                        return null;
                }
 
-               var committer = application.get_verified_committer();
-
-               if (committer == null)
-               {
-                       notification.error(_("Failed to obtain author details"));
-                       return null;
-               }
-
                string msg;
 
                if (source.parsed_name.rtype == RefType.REMOTE)
@@ -274,73 +266,18 @@ class RefActionMerge : GitgExt.UIElement, GitgExt.Action, GitgExt.RefAction, Obj
                        msg = @"Merge branch '$theirs_name'";
                }
 
-               var stage = application.repository.stage;
-
-               Gitg.Ref? head = null;
-               var ishead = d_support.reference_is_head(reference, ref head);
-
-               Ggit.OId? oid = null;
-               Ggit.Tree? head_tree = null;
-
-               if (ishead)
-               {
-                       if (!(yield d_support.stash_if_needed(notification, head)))
-                       {
-                               return null;
-                       }
-
-                       try
-                       {
-                               head_tree = (reference.lookup() as Ggit.Commit).get_tree();
-                       }
-                       catch (Error e)
-                       {
-                               notification.error(_("Failed to obtain HEAD tree: %s").printf(e.message));
-                               return null;
-                       }
-               }
-
-               try
-               {
-                       // TODO: not all hooks are being executed yet
-                       oid = yield stage.commit_index(index,
-                                                      ishead ? head : reference,
-                                                      msg,
-                                                      committer,
-                                                      committer,
-                                                      new Ggit.OId[] { ours.get_id(), theirs.get_id() },
-                                                      StageCommitOptions.NONE);
-               }
-               catch (Error e)
-               {
-                       notification.error(_("Failed to create commit: %s").printf(e.message));
-                       return null;
-               }
+               var oid = yield d_support.commit_index(notification,
+                                                      reference,
+                                                      index,
+                                                      new Ggit.OId[] { ours.get_id(), theirs.get_id() },
+                                                      null,
+                                                      msg);
 
-               if (ishead)
+               if (oid != null)
                {
-                       try
-                       {
-                               yield Async.thread(() => {
-                                       var opts = new Ggit.CheckoutOptions();
-
-                                       opts.set_strategy(Ggit.CheckoutStrategy.SAFE);
-                                       opts.set_baseline(head_tree);
-
-                                       var commit = application.repository.lookup<Ggit.Commit>(oid);
-                                       var tree = commit.get_tree();
-
-                                       application.repository.checkout_tree(tree, opts);
-                               });
-                       }
-                       catch (Error e)
-                       {
-                               notification.error(_("Failed to checkout index: %s").printf(e.message));
-                               return null;
-                       }
+                       notification.success(_("Successfully merged %s into %s").printf(@"'$theirs_name'", 
@"'$ours_name'"));
                }
 
-               notification.success(_("Successfully merged %s into %s").printf(@"'$theirs_name'", 
@"'$ours_name'"));
                return oid;
        }
 
diff --git a/gitg/history/gitg-history.vala b/gitg/history/gitg-history.vala
index a85b6cb..b2e3836 100644
--- a/gitg/history/gitg-history.vala
+++ b/gitg/history/gitg-history.vala
@@ -675,6 +675,11 @@ namespace GitgHistory
                                                                           af,
                                                                           commit));
 
+                       add_commit_action(actions,
+                                         new Gitg.CommitActionCherryPick(application,
+                                                                         af,
+                                                                         commit));
+
                        var exts = new Peas.ExtensionSet(Gitg.PluginsEngine.get_default(),
                                                         typeof(GitgExt.CommitAction),
                                                         "application",
diff --git a/tests/gitg/Makefile.am b/tests/gitg/Makefile.am
index b3925ca..7dfd6c1 100644
--- a/tests/gitg/Makefile.am
+++ b/tests/gitg/Makefile.am
@@ -49,12 +49,14 @@ TESTS_GITG_TEST_GITG_COPIED_SOURCES =                       \
        tests/gitg/support-repository.vala              \
        tests/gitg/gitg-ref-action-checkout.vala        \
        tests/gitg/gitg-ref-action-merge.vala           \
+       tests/gitg/gitg-commit-action-cherry-pick.vala  \
        tests/gitg/gitg-action-support.vala
 
 tests_gitg_test_gitg_SOURCES =                         \
        tests/gitg/main.vala                            \
        tests/gitg/test-checkout-ref.vala               \
        tests/gitg/test-merge-ref.vala                  \
+       tests/gitg/test-cherry-pick-commit.vala         \
        tests/gitg/simple-notification-mock.vala        \
        tests/gitg/application-mock.vala                \
        tests/gitg/notifications-mock.vala              \
diff --git a/tests/gitg/main.vala b/tests/gitg/main.vala
index 77a3498..f1b5255 100644
--- a/tests/gitg/main.vala
+++ b/tests/gitg/main.vala
@@ -24,7 +24,8 @@ class Gitg.Test.Runner
                var m = new Gitg.Test.Main(args);
 
                m.add(new CheckoutRef(),
-                     new MergeRef());
+                     new MergeRef(),
+                     new CherryPickCommit());
 
                m.run();
        }
diff --git a/tests/gitg/test-cherry-pick-commit.vala b/tests/gitg/test-cherry-pick-commit.vala
new file mode 100644
index 0000000..ee1959c
--- /dev/null
+++ b/tests/gitg/test-cherry-pick-commit.vala
@@ -0,0 +1,476 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2015 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gitg.Test.Assert;
+
+class Gitg.Test.CherryPickCommit : Application
+{
+       private Gitg.Branch ours;
+
+       private Gitg.Branch theirs;
+       private Gitg.Commit theirs_commit;
+
+       private Gitg.Branch master;
+       private Gitg.Commit master_commit;
+
+       private Gitg.Branch not_master;
+
+       private RefActionInterface action_interface;
+
+       protected override void set_up()
+       {
+               base.set_up();
+
+               commit("a", "a file\n");
+               create_branch("theirs");
+
+               commit("b", "b file\n");
+
+               checkout_branch("theirs");
+               commit("c", "c file\n");
+
+               theirs = lookup_branch("theirs");
+               theirs_commit = theirs.lookup() as Gitg.Commit;
+
+               checkout_branch("master");
+               not_master = create_branch("not_master");
+
+               master = lookup_branch("master");
+               master_commit = master.lookup() as Gitg.Commit;
+
+               action_interface = new RefActionInterface(this);
+       }
+
+       protected virtual signal void test_cherry_pick_simple()
+       {
+               var loop = new MainLoop();
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               action.cherry_pick.begin(master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'master'");
+               assert_streq(simple_notifications[0].message, "Successfully cherry picked");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "c file\n");
+
+               var commit = lookup_commit("master");
+
+               assert_streq(commit.get_message(), "commit c");
+               assert_streq(commit.get_id().to_string(), "87aef9f8f4320a9d997d194614d175254c24adc7");
+               assert_inteq((int)commit.get_author().get_time().to_unix(), 2);
+               assert_inteq((int)commit.get_committer().get_time().to_unix(), 3);
+       }
+
+       protected virtual signal void test_cherry_pick_not_head()
+       {
+               var loop = new MainLoop();
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               action.cherry_pick.begin(not_master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'not_master'");
+               assert_streq(simple_notifications[0].message, "Successfully cherry picked");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_true(!file_exists("c"));
+
+               var commit = lookup_commit("not_master");
+
+               assert_streq(commit.get_message(), "commit c");
+               assert_streq(commit.get_id().to_string(), "87aef9f8f4320a9d997d194614d175254c24adc7");
+               assert_inteq((int)commit.get_author().get_time().to_unix(), 2);
+               assert_inteq((int)commit.get_committer().get_time().to_unix(), 3);
+       }
+
+       protected virtual signal void test_cherry_pick_not_head_would_have_conflicted()
+       {
+               var loop = new MainLoop();
+
+               commit("c", "c file other content\n");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               action.cherry_pick.begin(not_master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'not_master'");
+               assert_streq(simple_notifications[0].message, "Successfully cherry picked");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "c file other content\n");
+
+               var commit = lookup_commit("not_master");
+
+               assert_streq(commit.get_message(), "commit c");
+               assert_streq(commit.get_id().to_string(), "e9e99c25e6061b42b6d48d143e028d1806f85745");
+               assert_inteq((int)commit.get_author().get_time().to_unix(), 2);
+               assert_inteq((int)commit.get_committer().get_time().to_unix(), 4);
+       }
+
+       protected virtual signal void test_cherry_pick_theirs_conflicts_no_checkout()
+       {
+               var loop = new MainLoop();
+
+               commit("c", "c file other content\n");
+               master = lookup_branch("master");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Cherry pick has conflicts",
+                                                           "The cherry pick of '72af7c' onto 'master' has 
caused conflicts, would you like to checkout branch 'master' with the cherry pick to your working directory 
to resolve the conflicts?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Checkout",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.CANCEL);
+
+               action.cherry_pick.begin(master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'master'");
+               assert_streq(simple_notifications[0].message, "Cherry pick failed with conflicts");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.ERROR);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "c file other content\n");
+
+               var commit = lookup_commit("master");
+
+               assert_streq(commit.get_message(), "commit c");
+               assert_streq(commit.get_id().to_string(), "e1219dd5fbcf8fb5b17bbd3db7a9fa88e98d6651");
+               assert_inteq((int)commit.get_author().get_time().to_unix(), 3);
+               assert_inteq((int)commit.get_committer().get_time().to_unix(), 3);
+       }
+
+       protected virtual signal void test_merge_theirs_conflicts_checkout()
+       {
+               var loop = new MainLoop();
+
+               commit("c", "c file other content\n");
+               master = lookup_branch("master");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Cherry pick has conflicts",
+                                                            "The cherry pick of '72af7c' onto 'master' has 
caused conflicts, would you like to checkout branch 'master' with the cherry pick to your working directory 
to resolve the conflicts?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Checkout",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               action.cherry_pick.begin(master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'master'");
+               assert_streq(simple_notifications[0].message, "Cherry pick finished with conflicts in working 
directory");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "<<<<<<< ours\nc file other content\n=======\nc file\n>>>>>>> 
theirs\n");
+
+               assert_file_contents(".git/CHERRY_PICK_HEAD", "72af7ccf47852d832b06c7244de8ae9ded639024\n");
+       }
+
+       protected virtual signal void test_cherry_pick_theirs_dirty_stash()
+       {
+               var loop = new MainLoop();
+
+               write_file("b", "b file other content\n");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Unstaged changes",
+                                                            "You appear to have unstaged changes in your 
working directory. Would you like to stash the changes before the checkout?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Stash changes",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               action.cherry_pick.begin(master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'master'");
+               assert_streq(simple_notifications[0].message, "Successfully cherry picked");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "c file\n");
+
+               var messages = new string[0];
+               var oids = new Ggit.OId[0];
+
+               d_repository.stash_foreach((index, message, oid) => {
+                       messages += message;
+                       oids += oid;
+
+                       return 0;
+               });
+
+               assert_inteq(messages.length, 1);
+               assert_streq(messages[0], "On master: WIP on HEAD: 50ac9b commit b");
+               assert_streq(oids[0].to_string(), "aaf63a72d8c0d5799ccfcf1623daef228968382f");
+
+               var commit = lookup_commit("master");
+
+               assert_streq(commit.get_message(), "commit c");
+               assert_streq(commit.get_id().to_string(), "87aef9f8f4320a9d997d194614d175254c24adc7");
+               assert_inteq((int)commit.get_author().get_time().to_unix(), 2);
+               assert_inteq((int)commit.get_committer().get_time().to_unix(), 3);
+       }
+
+       protected virtual signal void test_cherry_pick_theirs_not_master_conflicts_checkout()
+       {
+               var loop = new MainLoop();
+
+               checkout_branch("not_master");
+               commit("c", "c file other content\n");
+
+               not_master = lookup_branch("not_master");
+               checkout_branch("master");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Cherry pick has conflicts",
+                                                            "The cherry-pick of '72af7c' onto 'not_master' 
has caused conflicts, would you like to checkout the cherry pick to your working directory to resolve the 
conflicts?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Checkout",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               expect_user_query(new GitgExt.UserQuery.full("Unstaged changes",
+                                                            "You appear to have unstaged changes in your 
working directory. Would you like to stash the changes before the checkout?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Stash changes",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               action.cherry_pick.begin(not_master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 2);
+
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'not_master'");
+               assert_streq(simple_notifications[0].message, "Cherry pick finished with conflicts in working 
directory");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_streq(simple_notifications[1].title, "Checkout 'not_master'");
+               assert_streq(simple_notifications[1].message, "Successfully checked out branch to working 
directory");
+               assert_inteq(simple_notifications[1].status, SimpleNotification.Status.SUCCESS);
+
+               assert_streq(lookup_branch("HEAD").get_name(), "refs/heads/not_master");
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "<<<<<<< ours\nc file other content\n=======\nc file\n>>>>>>> 
theirs\n");
+
+               assert_file_contents(".git/CHERRY_PICK_HEAD", "72af7ccf47852d832b06c7244de8ae9ded639024\n");
+       }
+
+       protected virtual signal void test_cherry_pick_theirs_not_master_conflicts_checkout_dirty()
+       {
+               var loop = new MainLoop();
+
+               checkout_branch("not_master");
+               commit("c", "c file other content\n");
+
+               not_master = lookup_branch("not_master");
+               checkout_branch("master");
+
+               write_file("b", "b file other content\n");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Cherry pick has conflicts",
+                                                            "The cherry-pick of '72af7c' onto 'not_master' 
has caused conflicts, would you like to checkout the cherry pick to your working directory to resolve the 
conflicts?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Checkout",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               expect_user_query(new GitgExt.UserQuery.full("Unstaged changes",
+                                                            "You appear to have unstaged changes in your 
working directory. Would you like to stash the changes before the checkout?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Stash changes",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               action.cherry_pick.begin(not_master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 2);
+
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'not_master'");
+               assert_streq(simple_notifications[0].message, "Cherry pick finished with conflicts in working 
directory");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.SUCCESS);
+
+               assert_streq(simple_notifications[1].title, "Checkout 'not_master'");
+               assert_streq(simple_notifications[1].message, "Successfully checked out branch to working 
directory");
+               assert_inteq(simple_notifications[1].status, SimpleNotification.Status.SUCCESS);
+
+               assert_streq(lookup_branch("HEAD").get_name(), "refs/heads/not_master");
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file\n");
+               assert_file_contents("c", "<<<<<<< ours\nc file other content\n=======\nc file\n>>>>>>> 
theirs\n");
+
+               assert_file_contents(".git/CHERRY_PICK_HEAD", "72af7ccf47852d832b06c7244de8ae9ded639024\n");
+
+               var messages = new string[0];
+               var oids = new Ggit.OId[0];
+
+               d_repository.stash_foreach((index, message, oid) => {
+                       messages += message;
+                       oids += oid;
+
+                       return 0;
+               });
+
+               assert_inteq(messages.length, 1);
+               assert_streq(messages[0], "On master: WIP on HEAD");
+               assert_streq(oids[0].to_string(), "147b7b7b6ad2f9c90f4c93f3bfda78c78ec2dcde");
+       }
+
+       protected virtual signal void test_cherry_pick_theirs_not_master_conflicts_checkout_dirty_no_stash()
+       {
+               var loop = new MainLoop();
+
+               checkout_branch("not_master");
+               commit("c", "c file other content\n");
+
+               not_master = lookup_branch("not_master");
+               checkout_branch("master");
+
+               write_file("b", "b file other content\n");
+
+               var action = new Gitg.CommitActionCherryPick(this, action_interface, theirs_commit);
+
+               expect_user_query(new GitgExt.UserQuery.full("Cherry pick has conflicts",
+                                                            "The cherry-pick of '72af7c' onto 'not_master' 
has caused conflicts, would you like to checkout the cherry pick to your working directory to resolve the 
conflicts?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Checkout",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.OK);
+
+               expect_user_query(new GitgExt.UserQuery.full("Unstaged changes",
+                                                            "You appear to have unstaged changes in your 
working directory. Would you like to stash the changes before the checkout?",
+                                                            Gtk.MessageType.QUESTION,
+                                                            "Cancel",
+                                                            Gtk.ResponseType.CANCEL,
+                                                            "Stash changes",
+                                                            Gtk.ResponseType.OK),
+                                      Gtk.ResponseType.CANCEL);
+
+               action.cherry_pick.begin(not_master, (obj, res) => {
+                       action.cherry_pick.end(res);
+                       loop.quit();
+               });
+
+               loop.run();
+
+               assert_inteq(simple_notifications.size, 1);
+
+               assert_streq(simple_notifications[0].title, "Cherry pick '72af7c' onto 'not_master'");
+               assert_streq(simple_notifications[0].message, "Failed with conflicts");
+               assert_inteq(simple_notifications[0].status, SimpleNotification.Status.ERROR);
+
+               assert_streq(lookup_branch("HEAD").get_name(), "refs/heads/master");
+
+               assert_file_contents("a", "a file\n");
+               assert_file_contents("b", "b file other content\n");
+               assert(!file_exists("c"));
+
+               assert(!file_exists(".git/ORIG_HEAD"));
+               assert(!file_exists(".git/MERGE_HEAD"));
+               assert(!file_exists(".git/MERGE_MODE"));
+               assert(!file_exists(".git/MERGE_MSG"));
+
+               d_repository.stash_foreach((index, message, oid) => {
+                       assert(false);
+                       return 0;
+               });
+       }
+}
+
+// ex:set ts=4 noet
diff --git a/tests/gitg/test-merge-ref.vala b/tests/gitg/test-merge-ref.vala
index 006c99d..13137c3 100644
--- a/tests/gitg/test-merge-ref.vala
+++ b/tests/gitg/test-merge-ref.vala
@@ -271,6 +271,8 @@ class Gitg.Test.MergeRef : Application
                        return 0;
                });
 
+               assert_merged(ours_oid, theirs_oid, "master");
+
                assert_inteq(messages.length, 1);
                assert_streq(messages[0], "On master: WIP on HEAD: 50ac9b commit b");
                assert_streq(oids[0].to_string(), "aaf63a72d8c0d5799ccfcf1623daef228968382f");
@@ -458,7 +460,7 @@ class Gitg.Test.MergeRef : Application
                assert_inteq(simple_notifications.size, 1);
 
                assert_streq(simple_notifications[0].title, "Merge 'theirs' into 'not_master'");
-               assert_streq(simple_notifications[0].message, "Merge failed with conflicts");
+               assert_streq(simple_notifications[0].message, "Failed with conflicts");
                assert_inteq(simple_notifications[0].status, SimpleNotification.Status.ERROR);
 
                assert_streq(lookup_branch("HEAD").get_name(), "refs/heads/master");



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