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 23[Flags] 24public enum StageCommitOptions 25{ 26 NONE = 0, 27 SIGN_OFF = 1 << 0, 28 AMEND = 1 << 1, 29 SKIP_HOOKS = 1 << 2 30} 31 32public errordomain StageError 33{ 34 PRE_COMMIT_HOOK_FAILED, 35 COMMIT_MSG_HOOK_FAILED, 36 NOTHING_TO_COMMIT, 37 INDEX_ENTRY_NOT_FOUND 38} 39 40public class PatchSet 41{ 42 public enum Type 43 { 44 ADD = 'a', 45 REMOVE = 'r' 46 } 47 48 public struct Patch 49 { 50 Type type; 51 size_t old_offset; 52 size_t new_offset; 53 size_t length; 54 } 55 56 public string filename; 57 public Patch[] patches; 58 59 public PatchSet reversed() 60 { 61 var ret = new PatchSet(); 62 63 ret.filename = filename; 64 ret.patches = new Patch[patches.length]; 65 66 for (int i = 0; i < patches.length; i++) 67 { 68 var orig = patches[i]; 69 70 var p = Patch() { 71 old_offset = orig.new_offset, 72 new_offset = orig.old_offset, 73 length = orig.length 74 }; 75 76 switch (patches[i].type) 77 { 78 case Type.ADD: 79 p.type = Type.REMOVE; 80 break; 81 case Type.REMOVE: 82 p.type = Type.ADD; 83 break; 84 } 85 86 ret.patches[i] = p; 87 } 88 89 return ret; 90 } 91} 92 93public class Stage : Object 94{ 95 private weak Repository d_repository; 96 private Mutex d_index_mutex; 97 private Ggit.Tree? d_head_tree; 98 99 internal Stage(Repository repository) 100 { 101 d_repository = repository; 102 } 103 104 public async void refresh() throws Error 105 { 106 d_head_tree = null; 107 108 yield thread_index((index) => { 109 index.read(false); 110 }); 111 } 112 113 public async Ggit.Tree? get_head_tree() throws Error 114 { 115 if (d_head_tree != null) 116 { 117 return d_head_tree; 118 } 119 120 Error? e = null; 121 122 yield Async.thread(() => { 123 try 124 { 125 var head = d_repository.get_head(); 126 var commit = (Ggit.Commit)head.lookup(); 127 128 d_head_tree = commit.get_tree(); 129 } 130 catch (Error err) 131 { 132 e = err; 133 } 134 }); 135 136 if (e != null) 137 { 138 throw e; 139 } 140 141 return d_head_tree; 142 } 143 144 public StageStatusEnumerator file_status(Ggit.StatusOptions? options = null) 145 { 146 return new StageStatusEnumerator(d_repository, options); 147 } 148 149 private delegate void WithIndexFunc(Ggit.Index index) throws Error; 150 151 private void with_index(WithIndexFunc func) throws Error 152 { 153 lock(d_index_mutex) 154 { 155 func(d_repository.get_index()); 156 } 157 } 158 159 private async void thread_index(WithIndexFunc func) throws Error 160 { 161 yield Async.thread(() => { 162 with_index(func); 163 }); 164 } 165 166 private string message_with_sign_off(string message, 167 Ggit.Signature committer) 168 { 169 return "%s\nSigned-off-by: %s <%s>\n".printf(message, 170 committer.get_name(), 171 committer.get_email()); 172 } 173 174 private string convert_message_to_encoding(Ggit.Config conf, 175 string message, 176 out string? encoding) 177 { 178 encoding = null; 179 180 try 181 { 182 encoding = conf.get_string("i18n.commitencoding"); 183 } 184 catch 185 { 186 encoding = null; 187 return message; 188 } 189 190 if (encoding != null && 191 encoding != "" && 192 encoding.ascii_casecmp("UTF-8") != 0) 193 { 194 try 195 { 196 return convert(message, -1, encoding, "UTF-8"); 197 } 198 catch 199 { 200 encoding = null; 201 } 202 } 203 else 204 { 205 encoding = null; 206 } 207 208 return message; 209 } 210 211 private void setup_commit_hook_environment(Gitg.Hook hook, 212 Ggit.Signature? author) 213 { 214 var wd = d_repository.get_workdir(); 215 var gd = d_repository.get_location(); 216 217 hook.working_directory = wd; 218 219 var gitdir = wd.get_relative_path(gd); 220 221 hook.environment["GIT_DIR"] = gitdir; 222 hook.environment["GIT_INDEX_FILE"] = Path.build_filename(gitdir, "index"); 223 hook.environment["GIT_PREFIX"] = "."; 224 225 if (author != null) 226 { 227 hook.environment["GIT_AUTHOR_NAME"] = author.get_name(); 228 hook.environment["GIT_AUTHOR_EMAIL"] = author.get_email(); 229 230 var date = author.get_time(); 231 232 var un = date.to_unix(); 233 var tz = date.to_timezone(author.get_time_zone()).format("%z"); 234 235 hook.environment["GIT_AUTHOR_DATE"] = @"@$(un) $(tz)"; 236 } 237 } 238 239 public async void pre_commit_hook(Ggit.Signature author) throws StageError 240 { 241 string? errormsg = null; 242 243 try 244 { 245 yield Async.thread(() => { 246 // First run the pre-commit hook 247 var hook = new Gitg.Hook("pre-commit"); 248 249 setup_commit_hook_environment(hook, author); 250 251 try 252 { 253 int status = hook.run_sync(d_repository); 254 255 if (status != 0) 256 { 257 errormsg = string.joinv("\n", hook.output); 258 } 259 } 260 catch (SpawnError e) {} 261 }); 262 } catch {} 263 264 if (errormsg != null) 265 { 266 throw new StageError.PRE_COMMIT_HOOK_FAILED(errormsg); 267 } 268 } 269 270 private bool has_index_changes() 271 { 272 var show = Ggit.StatusShow.INDEX_ONLY; 273 274 var options = new Ggit.StatusOptions(0, show, null); 275 bool has_changes = false; 276 277 try 278 { 279 d_repository.file_status_foreach(options, (path, flags) => { 280 has_changes = true; 281 return -1; 282 }); 283 } catch {} 284 285 return has_changes; 286 } 287 288 private string commit_msg_hook(string message, 289 Ggit.Signature author, 290 Ggit.Signature committer) throws Error 291 { 292 var hook = new Gitg.Hook("commit-msg"); 293 294 if (!hook.exists_in(d_repository)) 295 { 296 return message; 297 } 298 299 setup_commit_hook_environment(hook, author); 300 301 var msgfile = d_repository.get_location().get_child("COMMIT_EDITMSG"); 302 303 try 304 { 305 FileUtils.set_contents(msgfile.get_path(), message); 306 } 307 catch { return message; } 308 309 hook.add_argument(msgfile.get_path()); 310 311 int status; 312 313 try 314 { 315 status = hook.run_sync(d_repository); 316 } 317 catch { return message; } 318 319 if (status != 0) 320 { 321 throw new StageError.COMMIT_MSG_HOOK_FAILED(string.joinv("\n", hook.output)); 322 } 323 324 // Read back the message 325 try 326 { 327 string newmessage; 328 329 FileUtils.get_contents(msgfile.get_path(), out newmessage); 330 return newmessage; 331 } 332 catch (Error e) 333 { 334 throw new StageError.COMMIT_MSG_HOOK_FAILED(_("Could not read commit message after running commit-msg hook: %s").printf(e.message)); 335 } 336 finally 337 { 338 FileUtils.remove(msgfile.get_path()); 339 } 340 } 341 342 private void post_commit_hook(Ggit.Signature author) 343 { 344 var hook = new Gitg.Hook("post-commit"); 345 346 setup_commit_hook_environment(hook, author); 347 348 hook.run.begin(d_repository, (obj, res) => { 349 try 350 { 351 hook.run.end(res); 352 } catch {} 353 }); 354 } 355 356 private string get_subject(string message) 357 { 358 var nlpos = message.index_of("\n"); 359 360 if (nlpos == -1) 361 { 362 return message; 363 } 364 else 365 { 366 return message[0:nlpos]; 367 } 368 } 369 370 public async Ggit.OId? commit_index(Ggit.Index index, 371 Ggit.Ref reference, 372 string message, 373 Ggit.Signature author, 374 Ggit.Signature committer, 375 Ggit.OId[]? parents, 376 StageCommitOptions options) throws Error 377 { 378 Ggit.OId? treeoid = null; 379 380 yield Async.thread(() => { 381 treeoid = index.write_tree_to(d_repository); 382 }); 383 384 return yield commit_tree(treeoid, reference, message, author, committer, parents, options); 385 } 386 387 public async Ggit.OId? commit_tree(Ggit.OId treeoid, 388 Ggit.Ref reference, 389 string message, 390 Ggit.Signature author, 391 Ggit.Signature committer, 392 Ggit.OId[]? parents, 393 StageCommitOptions options) throws Error 394 { 395 Ggit.OId? ret = null; 396 397 yield Async.thread(() => { 398 bool skip_hooks = (options & StageCommitOptions.SKIP_HOOKS) != 0; 399 bool amend = (options & StageCommitOptions.AMEND) != 0; 400 401 // Write tree from index 402 var conf = d_repository.get_config().snapshot(); 403 404 string emsg = message; 405 406 if ((options & StageCommitOptions.SIGN_OFF) != 0) 407 { 408 emsg = message_with_sign_off(emsg, committer); 409 } 410 411 string? encoding; 412 413 emsg = convert_message_to_encoding(conf, emsg, out encoding); 414 415 if (!skip_hooks) 416 { 417 emsg = commit_msg_hook(emsg, author, committer); 418 } 419 420 Ggit.OId? refoid = null; 421 422 try 423 { 424 // Resolve the ref and get the actual target id 425 refoid = reference.resolve().get_target(); 426 } catch {} 427 428 if (!amend) 429 { 430 Ggit.OId[] pars; 431 432 if (parents == null) 433 { 434 if (refoid == null) 435 { 436 pars = new Ggit.OId[] {}; 437 } 438 else 439 { 440 pars = new Ggit.OId[] { refoid }; 441 } 442 } 443 else 444 { 445 pars = parents; 446 } 447 448 ret = d_repository.create_commit_from_ids(reference.get_name(), 449 author, 450 committer, 451 encoding, 452 emsg, 453 treeoid, 454 pars); 455 } 456 else 457 { 458 var refcommit = d_repository.lookup<Ggit.Commit>(refoid); 459 var tree = d_repository.lookup<Ggit.Tree>(treeoid); 460 461 ret = refcommit.amend(reference.get_name(), 462 author, 463 committer, 464 encoding, 465 emsg, 466 tree); 467 } 468 469 bool always_update = false; 470 471 try 472 { 473 always_update = conf.get_bool("core.logAllRefUpdates"); 474 } catch {} 475 476 string reflogmsg = "commit"; 477 478 if (amend) 479 { 480 reflogmsg += " (amend)"; 481 } 482 483 reflogmsg += ": " + get_subject(message); 484 485 // Update reflog of reference 486 try 487 { 488 if (always_update || reference.has_log()) 489 { 490 var reflog = reference.get_log(); 491 reflog.append(ret, committer, reflogmsg); 492 reflog.write(); 493 } 494 } catch {} 495 496 if (reference.get_reference_type() == Ggit.RefType.SYMBOLIC) 497 { 498 // Update reflog of whereever HEAD points to 499 try 500 { 501 var resolved = reference.resolve(); 502 503 if (always_update || resolved.has_log()) 504 { 505 var reflog = resolved.get_log(); 506 507 reflog.append(ret, committer, reflogmsg); 508 reflog.write(); 509 } 510 } catch {} 511 } 512 513 if (reference.get_name() == "HEAD") 514 { 515 d_head_tree = null; 516 } 517 518 // run post commit 519 post_commit_hook(author); 520 }); 521 522 return ret; 523 } 524 525 public async Ggit.OId? commit(string message, 526 Ggit.Signature author, 527 Ggit.Signature committer, 528 StageCommitOptions options) throws Error 529 { 530 bool amend = (options & StageCommitOptions.AMEND) != 0; 531 Ggit.OId? ret = null; 532 533 lock(d_index_mutex) 534 { 535 Ggit.Index? index = null; 536 537 yield Async.thread(() => { 538 index = d_repository.get_index(); 539 }); 540 541 if (!amend && !has_index_changes()) 542 { 543 throw new StageError.NOTHING_TO_COMMIT("Nothing to commit"); 544 } 545 546 ret = yield commit_index(index, 547 d_repository.lookup_reference("HEAD"), 548 message, 549 author, 550 committer, 551 null, 552 options); 553 } 554 555 return ret; 556 } 557 558 /** 559 * Revert working directory changes. 560 * 561 * @param file the file to revert. 562 * 563 * Revert a file to the version currently recorded in HEAD. This will delete 564 * any modifications done in the current working directory to this file, 565 * so use with care! Note that this only affects the working directory, 566 * not the index. 567 */ 568 public async void revert(File file) throws Error 569 { 570 var tree = yield get_head_tree(); 571 572 yield thread_index((index) => { 573 // get path relative to the repository working directory 574 var wd = d_repository.get_workdir(); 575 var path = wd.get_relative_path(file); 576 577 // get the tree entry of that file 578 var entry = tree.get_by_path(path); 579 var id = entry.get_id(); 580 581 // resolve the blob 582 var blob = d_repository.lookup<Ggit.Blob>(id); 583 584 var stream = file.replace(null, false, FileCreateFlags.NONE); 585 586 stream.write_all(blob.get_raw_content(), null); 587 stream.close(); 588 }); 589 } 590 591 /** 592 * Revert working directory changes. 593 * 594 * @param path path relative to the working directory. 595 * 596 * Revert a path to the version currently recorded in HEAD. This will delete 597 * any modifications done in the current working directory to this file, 598 * so use with care! Note that this only affects the working directory, 599 * not the index. 600 */ 601 public async void revert_path(string path) throws Error 602 { 603 yield revert(d_repository.get_workdir().resolve_relative_path(path)); 604 } 605 606 /** 607 * Revert a patch in the working directory. 608 * 609 * @param patch the patch to revert. 610 * 611 * Revert a provided patch from the working directory. The patch should 612 * contain changes of the file in the current working directory to the contents 613 * of the index (i.e. as obtained from diff_workdir) 614 */ 615 public async void revert_patch(PatchSet patch) throws Error 616 { 617 // new file is the current file in the working directory 618 var workdirf = d_repository.get_workdir().resolve_relative_path(patch.filename); 619 var workdirf_stream = yield workdirf.read_async(); 620 621 yield thread_index((index) => { 622 var entries = index.get_entries(); 623 var entry = entries.get_by_path(workdirf, 0); 624 625 if (entry == null) 626 { 627 throw new StageError.INDEX_ENTRY_NOT_FOUND(patch.filename); 628 } 629 630 var index_blob = d_repository.lookup<Ggit.Blob>(entry.get_id()); 631 unowned uchar[] index_content = index_blob.get_raw_content(); 632 633 var index_stream = new MemoryInputStream.from_bytes(new Bytes(index_content)); 634 var reversed = patch.reversed(); 635 636 FileIOStream? out_stream = null; 637 File ?outf = null; 638 639 try 640 { 641 outf = File.new_tmp(null, out out_stream); 642 643 apply_patch_stream(workdirf_stream, 644 index_stream, 645 out_stream.output_stream, 646 reversed); 647 } 648 catch (Error e) 649 { 650 workdirf_stream.close(); 651 index_stream.close(); 652 653 if (outf != null) 654 { 655 try 656 { 657 outf.delete(); 658 } catch {} 659 } 660 661 throw e; 662 } 663 664 workdirf_stream.close(); 665 index_stream.close(); 666 667 if (out_stream != null) 668 { 669 out_stream.close(); 670 } 671 672 // Move outf to workdirf 673 try 674 { 675 var repl = workdirf.replace(null, 676 false, 677 FileCreateFlags.NONE); 678 679 repl.splice(outf.read(), 680 OutputStreamSpliceFlags.CLOSE_SOURCE | 681 OutputStreamSpliceFlags.CLOSE_TARGET); 682 } 683 catch (Error e) 684 { 685 try 686 { 687 outf.delete(); 688 } catch {} 689 690 throw e; 691 } 692 693 try 694 { 695 outf.delete(); 696 } catch {} 697 }); 698 } 699 700 /** 701 * Delete a file from the index. 702 * 703 * @param file the file to delete. 704 * 705 * Delete the file from the index. 706 */ 707 public async void @delete(File file) throws Error 708 { 709 yield thread_index((index) => { 710 index.remove(file, 0); 711 index.write(); 712 }); 713 } 714 715 /** 716 * Delete a relative path from the index. 717 * 718 * @param path path relative to the working directory. 719 * 720 * Delete the relative path from the index. 721 */ 722 public async void delete_path(string path) throws Error 723 { 724 yield this.delete(d_repository.get_workdir().resolve_relative_path(path)); 725 } 726 727 /** 728 * Stage a file to the index. 729 * 730 * @param file the file to stage. 731 * 732 * Stage the file to the index. This will record the state of the file in 733 * the working directory to the index. 734 */ 735 public async void stage(File file) throws Error 736 { 737 yield thread_index((index) => { 738 index.add_file(file); 739 index.write(); 740 }); 741 } 742 743 /** 744 * Stage a path to the index. 745 * 746 * @param path path relative to the working directory. 747 * 748 * Stage a relative path to the index. This will record the state of the file in 749 * the working directory to the index. 750 */ 751 public async void stage_path(string path) throws Error 752 { 753 yield stage(d_repository.get_workdir().resolve_relative_path(path)); 754 } 755 756 /** 757 * Stage a commit to the index. 758 * 759 * @param path path relative to the working directory. 760 * @param id the id of the commit object to stage at the given path. 761 * 762 * Stage a commit to the index with a relative path. This will record the 763 * given commit id for file pointed to at path in the index. 764 */ 765 public async void stage_commit(string path, Ggit.Commit commit) throws Error 766 { 767 yield thread_index((index) => { 768 var entry = d_repository.create_index_entry_for_path(path, commit.get_id()); 769 entry.set_commit(commit); 770 771 index.add(entry); 772 index.write(); 773 }); 774 } 775 776 private void copy_stream(OutputStream dest, InputStream src, ref size_t pos, size_t index, size_t length) throws Error 777 { 778 if (length == 0) 779 { 780 return; 781 } 782 783 var buf = new uint8[length]; 784 785 if (pos != index) 786 { 787 ((Seekable)src).seek(index, SeekType.SET); 788 pos = index; 789 } 790 791 src.read_all(buf, null); 792 dest.write_all(buf, null); 793 794 pos += length; 795 } 796 797 private void apply_patch(Ggit.Index index, 798 InputStream old_stream, 799 InputStream new_stream, 800 PatchSet patch) throws Error 801 { 802 var patched_stream = d_repository.create_blob(); 803 804 apply_patch_stream(old_stream, new_stream, patched_stream, patch); 805 806 patched_stream.close(); 807 var new_id = patched_stream.get_id(); 808 809 var new_entry = d_repository.create_index_entry_for_path(patch.filename, 810 new_id); 811 812 index.add(new_entry); 813 index.write(); 814 } 815 816 private void apply_patch_stream(InputStream old_stream, 817 InputStream new_stream, 818 OutputStream patched_stream, 819 PatchSet patch) throws Error 820 { 821 size_t old_ptr = 0; 822 size_t new_ptr = 0; 823 824 // Copy old_content to patched_stream while applying patches as 825 // specified in patch.patches from new_stream 826 foreach (var p in patch.patches) 827 { 828 // Copy from old_ptr until p.old_offset 829 copy_stream(patched_stream, 830 old_stream, 831 ref old_ptr, 832 old_ptr, 833 p.old_offset - old_ptr); 834 835 if (p.type == PatchSet.Type.REMOVE) 836 { 837 // Removing, just advance old stream 838 ((Seekable)old_stream).seek(p.length, SeekType.CUR); 839 old_ptr += p.length; 840 } 841 else 842 { 843 // Inserting, copy from new_stream 844 copy_stream(patched_stream, 845 new_stream, 846 ref new_ptr, 847 p.new_offset, 848 p.length); 849 } 850 } 851 852 // Copy remaining part of old 853 patched_stream.splice(old_stream, OutputStreamSpliceFlags.NONE); 854 } 855 856 /** 857 * Stage a patch to the index. 858 * 859 * @param patch the patch to stage. 860 * 861 * Stage a provided patch to the index. The patch should contain changes 862 * of the file in the current working directory to the contents of the 863 * index (i.e. as obtained from diff_workdir) 864 */ 865 public async void stage_patch(PatchSet patch) throws Error 866 { 867 // new file is the current file in the working directory 868 var newf = d_repository.get_workdir().resolve_relative_path(patch.filename); 869 var new_stream = yield newf.read_async(); 870 871 yield thread_index((index) => { 872 var entries = index.get_entries(); 873 var entry = entries.get_by_path(newf, 0); 874 875 if (entry == null) 876 { 877 throw new StageError.INDEX_ENTRY_NOT_FOUND(patch.filename); 878 } 879 880 var old_blob = d_repository.lookup<Ggit.Blob>(entry.get_id()); 881 unowned uchar[] old_content = old_blob.get_raw_content(); 882 883 var old_stream = new MemoryInputStream.from_bytes(new Bytes(old_content)); 884 885 apply_patch(index, old_stream, new_stream, patch); 886 887 new_stream.close(); 888 old_stream.close(); 889 }); 890 } 891 892 /** 893 * Unstage a file from the index. 894 * 895 * @param file the file to unstage. 896 * 897 * Unstage changes in the specified file from the index. This will record 898 * the state of the file in HEAD to the index. 899 */ 900 public async void unstage(File file) throws Error 901 { 902 var tree = yield get_head_tree(); 903 904 yield thread_index((index) => { 905 // get path relative to the repository working directory 906 var wd = d_repository.get_workdir(); 907 var path = wd.get_relative_path(file); 908 909 // get the tree entry of that file 910 var entry = tree.get_by_path(path); 911 var id = entry.get_id(); 912 913 // create a new index entry for the file, pointing to the blob 914 // from the HEAD tree 915 var ientry = d_repository.create_index_entry_for_path(path, id); 916 917 // override file mode since the file might not actually exist. 918 ientry.set_mode(entry.get_file_mode()); 919 920 index.add(ientry); 921 index.write(); 922 }); 923 } 924 925 /** 926 * Unstage a path from the index. 927 * 928 * @param path path relative to the working directory. 929 * 930 * Unstage changes in the specified relative path from the index. This will record 931 * the state of the file in HEAD to the index. 932 */ 933 public async void unstage_path(string path) throws Error 934 { 935 yield unstage(d_repository.get_workdir().resolve_relative_path(path)); 936 } 937 938 /** 939 * Unstage a patch from the index. 940 * 941 * @param patch the patch to unstage. 942 * 943 * Unstage a provided patch from the index. The patch should contain changes 944 * of the file in the index to the file in HEAD. 945 */ 946 public async void unstage_patch(PatchSet patch) throws Error 947 { 948 var file = d_repository.get_workdir().resolve_relative_path(patch.filename); 949 var tree = yield get_head_tree(); 950 951 yield thread_index((index) => { 952 var entries = index.get_entries(); 953 var entry = entries.get_by_path(file, 0); 954 955 if (entry == null) 956 { 957 throw new StageError.INDEX_ENTRY_NOT_FOUND(patch.filename); 958 } 959 960 var head_entry = tree.get_by_path(patch.filename); 961 var head_blob = d_repository.lookup<Ggit.Blob>(head_entry.get_id()); 962 var index_blob = d_repository.lookup<Ggit.Blob>(entry.get_id()); 963 964 unowned uchar[] head_content = head_blob.get_raw_content(); 965 unowned uchar[] index_content = index_blob.get_raw_content(); 966 967 var head_stream = new MemoryInputStream.from_bytes(new Bytes(head_content)); 968 var index_stream = new MemoryInputStream.from_bytes(new Bytes(index_content)); 969 970 var reversed = patch.reversed(); 971 972 apply_patch(index, index_stream, head_stream, reversed); 973 974 head_stream.close(); 975 index_stream.close(); 976 }); 977 } 978 979 public async Ggit.Diff? diff_index_all(StageStatusItem[]? files, 980 Ggit.DiffOptions? defopts = null) throws Error 981 { 982 var opts = new Ggit.DiffOptions(); 983 984 opts.flags = Ggit.DiffOption.INCLUDE_UNTRACKED | 985 Ggit.DiffOption.DISABLE_PATHSPEC_MATCH | 986 Ggit.DiffOption.RECURSE_UNTRACKED_DIRS; 987 988 989 if (files != null) 990 { 991 var pspec = new string[files.length]; 992 993 for (var i = 0; i < files.length; i++) 994 { 995 pspec[i] = files[i].path; 996 } 997 998 opts.pathspec = pspec; 999 } 1000 1001 if (defopts != null) 1002 { 1003 opts.flags |= defopts.flags; 1004 1005 opts.n_context_lines = defopts.n_context_lines; 1006 opts.n_interhunk_lines = defopts.n_interhunk_lines; 1007 1008 opts.old_prefix = defopts.old_prefix; 1009 opts.new_prefix = defopts.new_prefix; 1010 } 1011 1012 Ggit.Tree? tree = null; 1013 1014 if (!d_repository.is_empty()) 1015 { 1016 tree = yield get_head_tree(); 1017 } 1018 1019 return new Ggit.Diff.tree_to_index(d_repository, 1020 tree, 1021 d_repository.get_index(), 1022 opts); 1023 } 1024 1025 public async Ggit.Diff? diff_index(StageStatusItem f, 1026 Ggit.DiffOptions? defopts = null) throws Error 1027 { 1028 return yield diff_index_all(new StageStatusItem[] {f}, defopts); 1029 } 1030 1031 public async Ggit.Diff? diff_workdir_all(StageStatusItem[] files, 1032 Ggit.DiffOptions? defopts = null) throws Error 1033 { 1034 var opts = new Ggit.DiffOptions(); 1035 1036 opts.flags = Ggit.DiffOption.INCLUDE_UNTRACKED | 1037 Ggit.DiffOption.DISABLE_PATHSPEC_MATCH | 1038 Ggit.DiffOption.RECURSE_UNTRACKED_DIRS | 1039 Ggit.DiffOption.SHOW_UNTRACKED_CONTENT; 1040 1041 if (files != null) 1042 { 1043 var pspec = new string[files.length]; 1044 1045 for (var i = 0; i < files.length; i++) 1046 { 1047 pspec[i] = files[i].path; 1048 } 1049 1050 opts.pathspec = pspec; 1051 } 1052 1053 if (defopts != null) 1054 { 1055 opts.flags |= defopts.flags; 1056 1057 opts.n_context_lines = defopts.n_context_lines; 1058 opts.n_interhunk_lines = defopts.n_interhunk_lines; 1059 1060 opts.old_prefix = defopts.old_prefix; 1061 opts.new_prefix = defopts.new_prefix; 1062 } 1063 1064 return new Ggit.Diff.index_to_workdir(d_repository, 1065 d_repository.get_index(), 1066 opts); 1067 } 1068 1069 public async Ggit.Diff? diff_workdir(StageStatusItem f, 1070 Ggit.DiffOptions? defopts = null) throws Error 1071 { 1072 return yield diff_workdir_all(new StageStatusItem[] {f}, defopts); 1073 } 1074} 1075 1076} 1077 1078// ex:set ts=4 noet 1079