1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2013 - 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 interface StageStatusItem : Object 24{ 25 public abstract string path { owned get; } 26 27 public abstract bool is_staged { get; } 28 public abstract bool is_unstaged { get; } 29 public abstract bool is_untracked { get; } 30 31 public abstract string? icon_name { owned get; } 32} 33 34public class StageStatusFile : Object, StageStatusItem 35{ 36 private string d_path; 37 private Ggit.StatusFlags d_flags; 38 39 private static Ggit.StatusFlags s_index_flags = 40 Ggit.StatusFlags.INDEX_NEW 41 | Ggit.StatusFlags.INDEX_MODIFIED 42 | Ggit.StatusFlags.INDEX_DELETED 43 | Ggit.StatusFlags.INDEX_RENAMED 44 | Ggit.StatusFlags.INDEX_TYPECHANGE; 45 46 private static Ggit.StatusFlags s_work_flags = 47 Ggit.StatusFlags.WORKING_TREE_MODIFIED 48 | Ggit.StatusFlags.WORKING_TREE_DELETED 49 | Ggit.StatusFlags.WORKING_TREE_TYPECHANGE; 50 51 private static Ggit.StatusFlags s_untracked_flags = 52 Ggit.StatusFlags.WORKING_TREE_NEW; 53 54 public StageStatusFile(string path, Ggit.StatusFlags flags) 55 { 56 d_path = path; 57 d_flags = flags; 58 } 59 60 public string path 61 { 62 owned get { return d_path; } 63 } 64 65 public bool is_staged 66 { 67 get { return (d_flags & s_index_flags) != 0; } 68 } 69 70 public bool is_unstaged 71 { 72 get { return (d_flags & s_work_flags) != 0; } 73 } 74 75 public bool is_untracked 76 { 77 get { return (d_flags & s_untracked_flags) != 0; } 78 } 79 80 public Ggit.StatusFlags flags 81 { 82 get { return d_flags; } 83 } 84 85 private string? icon_for_status(Ggit.StatusFlags status) 86 { 87 if ((status & (Ggit.StatusFlags.INDEX_NEW | 88 Ggit.StatusFlags.WORKING_TREE_NEW)) != 0) 89 { 90 return "list-add-symbolic"; 91 } 92 else if ((status & (Ggit.StatusFlags.INDEX_MODIFIED | 93 Ggit.StatusFlags.INDEX_RENAMED | 94 Ggit.StatusFlags.INDEX_TYPECHANGE | 95 Ggit.StatusFlags.WORKING_TREE_MODIFIED | 96 Ggit.StatusFlags.WORKING_TREE_TYPECHANGE)) != 0) 97 { 98 return "text-editor-symbolic"; 99 } 100 else if ((status & (Ggit.StatusFlags.INDEX_DELETED | 101 Ggit.StatusFlags.WORKING_TREE_DELETED)) != 0) 102 { 103 return "edit-delete-symbolic"; 104 } 105 106 return null; 107 } 108 109 public string? icon_name 110 { 111 owned get { return icon_for_status(d_flags); } 112 } 113} 114 115public class StageStatusSubmodule : Object, StageStatusItem 116{ 117 private Ggit.Submodule d_submodule; 118 private string d_path; 119 private Ggit.SubmoduleStatus d_flags; 120 121 private static Ggit.SubmoduleStatus s_index_flags = 122 Ggit.SubmoduleStatus.INDEX_ADDED 123 | Ggit.SubmoduleStatus.INDEX_DELETED 124 | Ggit.SubmoduleStatus.INDEX_MODIFIED; 125 126 private static Ggit.SubmoduleStatus s_work_flags = 127 Ggit.SubmoduleStatus.WD_ADDED 128 | Ggit.SubmoduleStatus.WD_DELETED 129 | Ggit.SubmoduleStatus.WD_MODIFIED; 130 131 private static Ggit.SubmoduleStatus s_untracked_flags = 132 Ggit.SubmoduleStatus.IN_WD; 133 134 private static Ggit.SubmoduleStatus s_tracked_flags = 135 Ggit.SubmoduleStatus.IN_HEAD 136 | Ggit.SubmoduleStatus.IN_INDEX; 137 138 private static Ggit.SubmoduleStatus s_dirty_flags = 139 Ggit.SubmoduleStatus.WD_INDEX_MODIFIED 140 | Ggit.SubmoduleStatus.WD_WD_MODIFIED; 141 142 public StageStatusSubmodule(Ggit.Submodule submodule) 143 { 144 d_submodule = submodule; 145 d_path = submodule.get_path(); 146 147 var repository = submodule.get_owner(); 148 149 try 150 { 151 d_flags = repository.get_submodule_status(submodule.get_name(), 152 Ggit.SubmoduleIgnore.UNTRACKED); 153 } catch {} 154 } 155 156 public Ggit.Submodule submodule 157 { 158 get { return d_submodule; } 159 } 160 161 public string path 162 { 163 owned get { return d_path; } 164 } 165 166 public bool is_staged 167 { 168 get { return (d_flags & s_index_flags) != 0; } 169 } 170 171 public bool is_unstaged 172 { 173 get { return !is_untracked && (d_flags & s_work_flags) != 0; } 174 } 175 176 public bool is_untracked 177 { 178 get 179 { 180 return (d_flags & s_untracked_flags) != 0 181 && (d_flags & s_tracked_flags) == 0; 182 } 183 } 184 185 public bool is_dirty 186 { 187 get { return (d_flags & s_dirty_flags) != 0; } 188 } 189 190 public Ggit.SubmoduleStatus flags 191 { 192 get { return d_flags; } 193 } 194 195 public string? icon_name { 196 owned get { return "folder-remote-symbolic"; } 197 } 198} 199 200public class StageStatusEnumerator : Object 201{ 202 private Repository d_repository; 203 private Thread<void *> d_thread; 204 private StageStatusItem[] d_items; 205 private int d_offset; 206 private int d_callback_num; 207 private Cancellable d_cancellable; 208 private SourceFunc d_callback; 209 private Ggit.StatusOptions? d_options; 210 private Gee.HashSet<string> d_ignored_submodules; 211 212 private static Regex s_ignore_regex; 213 214 static construct 215 { 216 try 217 { 218 s_ignore_regex = new Regex("submodule\\.(.*)\\.gitgignore"); 219 } 220 catch (Error e) 221 { 222 stderr.printf(@"Failed to compile stage status enumerator regex: $(e.message)\n"); 223 } 224 } 225 226 internal StageStatusEnumerator(Repository repository, 227 Ggit.StatusOptions? options = null) 228 { 229 d_repository = repository; 230 d_options = options; 231 232 d_items = new StageStatusItem[100]; 233 d_items.length = 0; 234 d_cancellable = new Cancellable(); 235 236 try 237 { 238 d_ignored_submodules = new Gee.HashSet<string>(); 239 240 repository.get_config().snapshot().match_foreach(s_ignore_regex, (match, val) => { 241 if (val != "true") 242 { 243 return 0; 244 } 245 246 d_ignored_submodules.add(match.fetch(1)); 247 return 0; 248 }); 249 } catch {} 250 251 try 252 { 253 d_thread = new Thread<void *>.try("gitg-status-enumerator", run_status); 254 } catch {} 255 } 256 257 public void cancel() 258 { 259 lock (d_items) 260 { 261 if (d_cancellable != null) 262 { 263 d_cancellable.cancel(); 264 } 265 } 266 267 if (d_thread != null) 268 { 269 d_thread.join(); 270 d_thread = null; 271 } 272 } 273 274 private delegate void AddItem(StageStatusItem item); 275 276 private void *run_status() 277 { 278 AddItem add = (item) => { 279 lock (d_items) 280 { 281 d_items += item; 282 283 if (d_callback != null && d_callback_num != -1 && d_items.length >= d_callback_num) 284 { 285 var cb = (owned)d_callback; 286 d_callback = null; 287 288 Idle.add((owned)cb); 289 } 290 } 291 }; 292 293 var submodule_paths = new Gee.HashSet<string>(); 294 295 // Due to a bug in libgit2, submodule iteration crashes when performed 296 // on a bare repository 297 if (!d_repository.is_bare) 298 { 299 try 300 { 301 d_repository.submodule_foreach((submodule, name) => { 302 submodule_paths.add(submodule.get_path()); 303 304 if (!d_ignored_submodules.contains(name)) 305 { 306 try 307 { 308 add(new StageStatusSubmodule(d_repository.lookup_submodule(name))); 309 } catch {} 310 } 311 312 return d_cancellable.is_cancelled() ? 1 : 0; 313 }); 314 } catch {} 315 } 316 317 try 318 { 319 d_repository.file_status_foreach(d_options, (path, flags) => { 320 if (!submodule_paths.contains(path)) 321 { 322 add(new StageStatusFile(path, flags)); 323 } 324 325 return d_cancellable.is_cancelled() ? 1 : 0; 326 }); 327 } catch {} 328 329 lock (d_items) 330 { 331 d_cancellable = null; 332 333 if (d_callback != null && d_callback_num == -1) 334 { 335 var cb = (owned)d_callback; 336 d_callback = null; 337 338 Idle.add((owned)cb); 339 } 340 } 341 342 return null; 343 } 344 345 private StageStatusItem[] fill_items(int num) 346 { 347 int n = 0; 348 349 if (num == -1) 350 { 351 num = d_items.length - d_offset; 352 } 353 354 StageStatusItem[] ret = new StageStatusItem[int.min(num, d_items.length - d_offset)]; 355 ret.length = 0; 356 357 // d_items is already locked here, so it's safe to access 358 while (d_offset < d_items.length) 359 { 360 if (n == num) 361 { 362 break; 363 } 364 365 ret += d_items[d_offset]; 366 d_offset++; 367 368 ++n; 369 } 370 371 return ret; 372 } 373 374 public async StageStatusItem[] next_items(int num) 375 { 376 SourceFunc callback = next_items.callback; 377 StageStatusItem[] ret; 378 379 lock (d_items) 380 { 381 if (d_cancellable == null) 382 { 383 // Already finished 384 return fill_items(num); 385 } 386 else 387 { 388 d_callback = (owned)callback; 389 d_callback_num = num; 390 } 391 } 392 393 yield; 394 395 lock (d_items) 396 { 397 ret = fill_items(num); 398 } 399 400 if (ret.length != num) 401 { 402 cancel(); 403 } 404 405 return ret; 406 } 407} 408 409} 410 411// ex:set ts=4 noet 412