1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2015 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace Gitg 21{ 22 23public class ActionSupport : Object 24{ 25 // Do this to pull in config.h before glib.h (for gettext...) 26 private const string version = Gitg.Config.VERSION; 27 28 public GitgExt.Application? application { owned get; construct set; } 29 public GitgExt.RefActionInterface action_interface { owned get; construct set; } 30 31 public ActionSupport(GitgExt.Application application, GitgExt.RefActionInterface action_interface) 32 { 33 Object(application: application, action_interface: action_interface); 34 } 35 36 public async bool working_directory_dirty() 37 { 38 var options = new Ggit.StatusOptions(Ggit.StatusOption.EXCLUDE_SUBMODULES, 39 Ggit.StatusShow.WORKDIR_ONLY, 40 null); 41 var is_dirty = false; 42 43 yield Async.thread_try(() => { 44 application.repository.file_status_foreach(options, (path, flags) => { 45 is_dirty = true; 46 return -1; 47 }); 48 }); 49 50 return is_dirty; 51 } 52 53 public async bool save_stash(SimpleNotification notification, Gitg.Ref? head) 54 { 55 var committer = application.get_verified_committer(); 56 57 if (committer == null) 58 { 59 return false; 60 } 61 62 try 63 { 64 yield Async.thread(() => { 65 // Try to stash changes 66 string message; 67 68 if (head != null) 69 { 70 var headname = head.parsed_name.shortname; 71 72 try 73 { 74 var head_commit = head.resolve().lookup() as Ggit.Commit; 75 var shortid = head_commit.get_id().to_string()[0:6]; 76 var subject = head_commit.get_subject(); 77 78 message = @"WIP on $(headname): $(shortid) $(subject)"; 79 } 80 catch 81 { 82 message = @"WIP on $(headname)"; 83 } 84 } 85 else 86 { 87 message = "WIP on HEAD"; 88 } 89 90 application.repository.save_stash(committer, message, Ggit.StashFlags.DEFAULT); 91 }); 92 } 93 catch (Error err) 94 { 95 notification.error(_("Failed to stash changes: %s").printf(err.message)); 96 return false; 97 } 98 99 return true; 100 } 101 102 public bool reference_is_head(Gitg.Ref reference, ref Gitg.Ref? head) 103 { 104 var branch = reference as Ggit.Branch; 105 head = null; 106 107 if (branch == null) 108 { 109 return false; 110 } 111 112 try 113 { 114 if (!branch.is_head()) 115 { 116 return false; 117 } 118 119 head = application.repository.lookup_reference("HEAD"); 120 } catch {} 121 122 return head != null; 123 } 124 125 public async bool stash_if_needed(SimpleNotification notification, Gitg.Ref head) 126 { 127 // Offer to stash if there are any local changes 128 if ((yield working_directory_dirty())) 129 { 130 var q = new GitgExt.UserQuery.full(_("Unstaged changes"), 131 _("You appear to have unstaged changes in your working directory. Would you like to stash the changes before the checkout?"), 132 Gtk.MessageType.QUESTION, 133 _("Cancel"), Gtk.ResponseType.CANCEL, 134 _("Stash changes"), Gtk.ResponseType.OK); 135 136 if ((yield application.user_query_async(q)) != Gtk.ResponseType.OK) 137 { 138 notification.error(_("Failed with conflicts")); 139 return false; 140 } 141 142 if (!(yield save_stash(notification, head))) 143 { 144 return false; 145 } 146 } 147 148 return true; 149 } 150 151 public async bool checkout_conflicts(SimpleNotification notification, Gitg.Ref reference, Ggit.Index index, Gitg.Ref? head) 152 { 153 if (!(yield stash_if_needed(notification, head))) 154 { 155 return false; 156 } 157 158 if (head == null) 159 { 160 // Perform checkout of the local branch first 161 var checkout = new RefActionCheckout(application, action_interface, reference); 162 163 if (!(yield checkout.checkout())) 164 { 165 notification.error(_("Failed with conflicts")); 166 return false; 167 } 168 } 169 170 // Finally, checkout the conflicted index 171 try 172 { 173 yield Async.thread(() => { 174 var opts = new Ggit.CheckoutOptions(); 175 opts.set_strategy(Ggit.CheckoutStrategy.SAFE); 176 application.repository.checkout_index(index, opts); 177 }); 178 } 179 catch (Error err) 180 { 181 notification.error(_("Failed to checkout conflicts: %s").printf(err.message)); 182 return false; 183 } 184 185 return true; 186 } 187 188 public async Ggit.OId? commit_index(SimpleNotification notification, 189 Gitg.Ref reference, 190 Ggit.Index index, 191 owned Ggit.OId[]? parents, 192 Ggit.Signature? author, 193 string message) 194 { 195 var committer = application.get_verified_committer(); 196 197 if (committer == null) 198 { 199 notification.error(_("Failed to obtain author details")); 200 return null; 201 } 202 203 if (author == null) 204 { 205 author = committer; 206 } 207 208 var stage = application.repository.stage; 209 210 Gitg.Ref? head = null; 211 var ishead = reference_is_head(reference, ref head); 212 213 Ggit.OId? oid = null; 214 Ggit.Tree? head_tree = null; 215 Gitg.Commit? commit = null; 216 217 try 218 { 219 commit = reference.lookup() as Gitg.Commit; 220 } 221 catch (Error e) 222 { 223 notification.error(_("Failed to lookup commit: %s").printf(e.message)); 224 return null; 225 } 226 227 if (ishead) 228 { 229 if (!(yield stash_if_needed(notification, head))) 230 { 231 return null; 232 } 233 234 head_tree = commit.get_tree(); 235 } 236 237 if (parents == null) 238 { 239 parents = new Ggit.OId[] { commit.get_id() }; 240 } 241 242 try 243 { 244 // TODO: not all hooks are being executed yet 245 oid = yield stage.commit_index(index, 246 ishead ? head : reference, 247 message, 248 author, 249 committer, 250 parents, 251 StageCommitOptions.NONE); 252 } 253 catch (Error e) 254 { 255 notification.error(_("Failed to create commit: %s").printf(e.message)); 256 return null; 257 } 258 259 if (ishead) 260 { 261 try 262 { 263 yield Async.thread(() => { 264 var opts = new Ggit.CheckoutOptions(); 265 266 opts.set_strategy(Ggit.CheckoutStrategy.SAFE); 267 opts.set_baseline(head_tree); 268 269 var newcommit = application.repository.lookup<Ggit.Commit>(oid); 270 var newtree = newcommit.get_tree(); 271 272 application.repository.checkout_tree(newtree, opts); 273 }); 274 } 275 catch (Error e) 276 { 277 notification.error(_("Failed to checkout index: %s").printf(e.message)); 278 return null; 279 } 280 } 281 282 return oid; 283 } 284} 285 286} 287 288// ex:set ts=4 noet 289