@@ -5,6 +5,82 @@ module git_module |
| 5 | 5 | |
| 6 | 6 | contains |
| 7 | 7 | |
| 8 | + ! ========== Performance Optimization: Binary Search Functions ========== |
| 9 | + |
| 10 | + recursive subroutine quicksort_files(arr, low, high) |
| 11 | + type(file_entry), intent(inout) :: arr(:) |
| 12 | + integer, intent(in) :: low, high |
| 13 | + integer :: pivot_idx |
| 14 | + |
| 15 | + if (low < high) then |
| 16 | + call partition_files(arr, low, high, pivot_idx) |
| 17 | + call quicksort_files(arr, low, pivot_idx - 1) |
| 18 | + call quicksort_files(arr, pivot_idx + 1, high) |
| 19 | + end if |
| 20 | + end subroutine quicksort_files |
| 21 | + |
| 22 | + subroutine partition_files(arr, low, high, pivot_idx) |
| 23 | + type(file_entry), intent(inout) :: arr(:) |
| 24 | + integer, intent(in) :: low, high |
| 25 | + integer, intent(out) :: pivot_idx |
| 26 | + character(len=512) :: pivot_path |
| 27 | + type(file_entry) :: temp |
| 28 | + integer :: i, j |
| 29 | + |
| 30 | + pivot_path = trim(arr(high)%path) |
| 31 | + i = low - 1 |
| 32 | + |
| 33 | + do j = low, high - 1 |
| 34 | + if (trim(arr(j)%path) <= pivot_path) then |
| 35 | + i = i + 1 |
| 36 | + ! Swap arr(i) and arr(j) |
| 37 | + temp = arr(i) |
| 38 | + arr(i) = arr(j) |
| 39 | + arr(j) = temp |
| 40 | + end if |
| 41 | + end do |
| 42 | + |
| 43 | + ! Swap arr(i+1) and arr(high) |
| 44 | + temp = arr(i + 1) |
| 45 | + arr(i + 1) = arr(high) |
| 46 | + arr(high) = temp |
| 47 | + |
| 48 | + pivot_idx = i + 1 |
| 49 | + end subroutine partition_files |
| 50 | + |
| 51 | + function binary_search_file(arr, n, target_path) result(index) |
| 52 | + type(file_entry), intent(in) :: arr(:) |
| 53 | + integer, intent(in) :: n |
| 54 | + character(len=*), intent(in) :: target_path |
| 55 | + integer :: index |
| 56 | + integer :: low, high, mid |
| 57 | + character(len=512) :: target_trimmed, mid_path |
| 58 | + |
| 59 | + index = -1 ! Not found |
| 60 | + if (n == 0) return |
| 61 | + |
| 62 | + target_trimmed = trim(target_path) |
| 63 | + low = 1 |
| 64 | + high = n |
| 65 | + |
| 66 | + do while (low <= high) |
| 67 | + mid = low + (high - low) / 2 |
| 68 | + mid_path = trim(arr(mid)%path) |
| 69 | + |
| 70 | + if (mid_path == target_trimmed) then |
| 71 | + index = mid |
| 72 | + return |
| 73 | + else if (mid_path < target_trimmed) then |
| 74 | + low = mid + 1 |
| 75 | + else |
| 76 | + high = mid - 1 |
| 77 | + end if |
| 78 | + end do |
| 79 | + end function binary_search_file |
| 80 | + |
| 81 | + ! ========== End Binary Search Functions ========== |
| 82 | + |
| 83 | + |
| 8 | 84 | subroutine get_dirty_files(files, n_files) |
| 9 | 85 | type(file_entry), allocatable, intent(out) :: files(:) |
| 10 | 86 | integer, intent(out) :: n_files |
@@ -77,7 +153,11 @@ contains |
| 77 | 153 | |
| 78 | 154 | ! Copy to output array |
| 79 | 155 | allocate(files(n_files)) |
| 80 | | - if (n_files > 0) files(1:n_files) = temp_files(1:n_files) |
| 156 | + if (n_files > 0) then |
| 157 | + files(1:n_files) = temp_files(1:n_files) |
| 158 | + ! Sort for binary search optimization |
| 159 | + call quicksort_files(files, 1, n_files) |
| 160 | + end if |
| 81 | 161 | deallocate(temp_files) |
| 82 | 162 | end subroutine get_dirty_files |
| 83 | 163 | |
@@ -137,26 +217,23 @@ contains |
| 137 | 217 | call resize_array(temp_files, max_files) |
| 138 | 218 | end if |
| 139 | 219 | |
| 140 | | - ! Check if file is dirty and get status |
| 141 | | - is_dirty_file = .false. |
| 220 | + ! Check if file is dirty and get status using binary search (O(log n) vs O(n)) |
| 221 | + temp_files(n_files)%path = trim(line) |
| 142 | 222 | temp_files(n_files)%status = ' ' ! Initialize as clean |
| 143 | 223 | temp_files(n_files)%is_staged = .false. |
| 144 | 224 | temp_files(n_files)%is_unstaged = .false. |
| 145 | 225 | temp_files(n_files)%is_untracked = .false. |
| 146 | 226 | temp_files(n_files)%has_incoming = .false. |
| 147 | | - do i = 1, n_dirty |
| 148 | | - if (trim(dirty_files(i)%path) == trim(line)) then |
| 149 | | - is_dirty_file = .true. |
| 150 | | - temp_files(n_files)%status = dirty_files(i)%status |
| 151 | | - temp_files(n_files)%is_staged = dirty_files(i)%is_staged |
| 152 | | - temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged |
| 153 | | - temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked |
| 154 | | - temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming |
| 155 | | - exit |
| 156 | | - end if |
| 157 | | - end do |
| 158 | 227 | |
| 159 | | - temp_files(n_files)%path = trim(line) |
| 228 | + i = binary_search_file(dirty_files, n_dirty, line) |
| 229 | + if (i > 0) then |
| 230 | + ! Found in dirty files - copy status |
| 231 | + temp_files(n_files)%status = dirty_files(i)%status |
| 232 | + temp_files(n_files)%is_staged = dirty_files(i)%is_staged |
| 233 | + temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged |
| 234 | + temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked |
| 235 | + temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming |
| 236 | + end if |
| 160 | 237 | end if |
| 161 | 238 | end do |
| 162 | 239 | |
@@ -587,9 +664,10 @@ contains |
| 587 | 664 | subroutine mark_incoming_changes(files, n_files) |
| 588 | 665 | type(file_entry), intent(inout) :: files(:) |
| 589 | 666 | integer, intent(in) :: n_files |
| 590 | | - integer :: iostat, unit_num, status_code, i |
| 667 | + integer :: iostat, unit_num, status_code, i, idx |
| 591 | 668 | character(len=1024) :: line |
| 592 | | - character(len=512) :: incoming_path |
| 669 | + character(len=512), allocatable :: incoming_paths(:) |
| 670 | + integer :: n_incoming, max_incoming |
| 593 | 671 | |
| 594 | 672 | ! Check if there's an upstream branch configured |
| 595 | 673 | ! Don't prompt - this is called automatically during refresh |
@@ -611,25 +689,131 @@ contains |
| 611 | 689 | open(newunit=unit_num, file='/tmp/fuss_incoming.txt', status='old', action='read', iostat=iostat) |
| 612 | 690 | if (iostat /= 0) return |
| 613 | 691 | |
| 692 | + ! Build sorted array of incoming file paths for binary search |
| 693 | + max_incoming = 100 |
| 694 | + allocate(incoming_paths(max_incoming)) |
| 695 | + n_incoming = 0 |
| 696 | + |
| 614 | 697 | do |
| 615 | 698 | read(unit_num, '(A)', iostat=iostat) line |
| 616 | 699 | if (iostat /= 0) exit |
| 617 | 700 | |
| 618 | 701 | if (len_trim(line) > 0) then |
| 619 | | - incoming_path = trim(line) |
| 620 | | - ! Mark this file as having incoming changes |
| 621 | | - do i = 1, n_files |
| 622 | | - if (trim(files(i)%path) == trim(incoming_path)) then |
| 623 | | - files(i)%has_incoming = .true. |
| 624 | | - exit |
| 625 | | - end if |
| 626 | | - end do |
| 702 | + n_incoming = n_incoming + 1 |
| 703 | + if (n_incoming > max_incoming) then |
| 704 | + ! Resize array |
| 705 | + call resize_string_array(incoming_paths, max_incoming * 2) |
| 706 | + max_incoming = max_incoming * 2 |
| 707 | + end if |
| 708 | + incoming_paths(n_incoming) = trim(line) |
| 627 | 709 | end if |
| 628 | 710 | end do |
| 629 | 711 | |
| 630 | 712 | close(unit_num, status='delete') |
| 713 | + |
| 714 | + if (n_incoming == 0) then |
| 715 | + deallocate(incoming_paths) |
| 716 | + return |
| 717 | + end if |
| 718 | + |
| 719 | + ! Sort incoming paths for binary search |
| 720 | + call quicksort_strings(incoming_paths, 1, n_incoming) |
| 721 | + |
| 722 | + ! Use binary search to mark files with incoming changes (O(n log m) vs O(n×m)) |
| 723 | + do i = 1, n_files |
| 724 | + idx = binary_search_string(incoming_paths, n_incoming, files(i)%path) |
| 725 | + if (idx > 0) then |
| 726 | + files(i)%has_incoming = .true. |
| 727 | + end if |
| 728 | + end do |
| 729 | + |
| 730 | + deallocate(incoming_paths) |
| 631 | 731 | end subroutine mark_incoming_changes |
| 632 | 732 | |
| 733 | + ! Helper subroutines for string array sorting and searching |
| 734 | + subroutine resize_string_array(array, new_size) |
| 735 | + character(len=512), allocatable, intent(inout) :: array(:) |
| 736 | + integer, intent(in) :: new_size |
| 737 | + character(len=512), allocatable :: temp(:) |
| 738 | + integer :: old_size |
| 739 | + |
| 740 | + old_size = size(array) |
| 741 | + allocate(temp(old_size)) |
| 742 | + temp = array |
| 743 | + deallocate(array) |
| 744 | + allocate(array(new_size)) |
| 745 | + array(1:old_size) = temp |
| 746 | + deallocate(temp) |
| 747 | + end subroutine resize_string_array |
| 748 | + |
| 749 | + recursive subroutine quicksort_strings(arr, low, high) |
| 750 | + character(len=512), intent(inout) :: arr(:) |
| 751 | + integer, intent(in) :: low, high |
| 752 | + integer :: pivot_idx |
| 753 | + |
| 754 | + if (low < high) then |
| 755 | + call partition_strings(arr, low, high, pivot_idx) |
| 756 | + call quicksort_strings(arr, low, pivot_idx - 1) |
| 757 | + call quicksort_strings(arr, pivot_idx + 1, high) |
| 758 | + end if |
| 759 | + end subroutine quicksort_strings |
| 760 | + |
| 761 | + subroutine partition_strings(arr, low, high, pivot_idx) |
| 762 | + character(len=512), intent(inout) :: arr(:) |
| 763 | + integer, intent(in) :: low, high |
| 764 | + integer, intent(out) :: pivot_idx |
| 765 | + character(len=512) :: pivot, temp |
| 766 | + integer :: i, j |
| 767 | + |
| 768 | + pivot = trim(arr(high)) |
| 769 | + i = low - 1 |
| 770 | + |
| 771 | + do j = low, high - 1 |
| 772 | + if (trim(arr(j)) <= pivot) then |
| 773 | + i = i + 1 |
| 774 | + temp = arr(i) |
| 775 | + arr(i) = arr(j) |
| 776 | + arr(j) = temp |
| 777 | + end if |
| 778 | + end do |
| 779 | + |
| 780 | + temp = arr(i + 1) |
| 781 | + arr(i + 1) = arr(high) |
| 782 | + arr(high) = temp |
| 783 | + |
| 784 | + pivot_idx = i + 1 |
| 785 | + end subroutine partition_strings |
| 786 | + |
| 787 | + function binary_search_string(arr, n, target) result(index) |
| 788 | + character(len=512), intent(in) :: arr(:) |
| 789 | + integer, intent(in) :: n |
| 790 | + character(len=*), intent(in) :: target |
| 791 | + integer :: index |
| 792 | + integer :: low, high, mid |
| 793 | + character(len=512) :: target_trimmed, mid_val |
| 794 | + |
| 795 | + index = -1 |
| 796 | + if (n == 0) return |
| 797 | + |
| 798 | + target_trimmed = trim(target) |
| 799 | + low = 1 |
| 800 | + high = n |
| 801 | + |
| 802 | + do while (low <= high) |
| 803 | + mid = low + (high - low) / 2 |
| 804 | + mid_val = trim(arr(mid)) |
| 805 | + |
| 806 | + if (mid_val == target_trimmed) then |
| 807 | + index = mid |
| 808 | + return |
| 809 | + else if (mid_val < target_trimmed) then |
| 810 | + low = mid + 1 |
| 811 | + else |
| 812 | + high = mid - 1 |
| 813 | + end if |
| 814 | + end do |
| 815 | + end function binary_search_string |
| 816 | + |
| 633 | 817 | subroutine git_fetch() |
| 634 | 818 | integer :: status |
| 635 | 819 | logical :: upstream_set |