@@ -108,13 +108,15 @@ contains |
| 108 | 108 | is_cache_valid = files_match |
| 109 | 109 | end function is_cache_valid |
| 110 | 110 | |
| 111 | | - subroutine get_git_status(dir, files, count, is_staged, is_unstaged, is_untracked) |
| 111 | + subroutine get_git_status(dir, files, is_dir_arr, count, is_staged, is_unstaged, is_untracked) |
| 112 | 112 | character(len=*), intent(in) :: dir |
| 113 | 113 | character(len=*), dimension(*), intent(in) :: files |
| 114 | + logical, dimension(*), intent(in) :: is_dir_arr |
| 114 | 115 | integer, intent(in) :: count |
| 115 | 116 | logical, dimension(*), intent(out) :: is_staged, is_unstaged, is_untracked |
| 116 | 117 | character(len=MAX_PATH) :: temp_file, line, file_path, git_status |
| 117 | | - integer :: unit, ios, stat, i |
| 118 | + character(len=MAX_PATH) :: repo_root, rel_path, full_status_path |
| 119 | + integer :: unit, ios, stat, i, repo_root_len |
| 118 | 120 | |
| 119 | 121 | ! Check if we can use cached data |
| 120 | 122 | if (is_cache_valid(dir, files, count)) then |
@@ -134,10 +136,35 @@ contains |
| 134 | 136 | is_untracked(i) = .false. |
| 135 | 137 | end do |
| 136 | 138 | |
| 137 | | - ! Get git status |
| 139 | + ! Get repo root |
| 138 | 140 | call get_environment_variable("HOME", temp_file) |
| 139 | | - temp_file = trim(temp_file) // "/.fortress_git_status" |
| 140 | | - call execute_command_line("cd '" // trim(dir) // "' && git status --porcelain 2>/dev/null > " // & |
| 141 | + temp_file = trim(temp_file) // "/.fortress_repo_root" |
| 142 | + call execute_command_line("git -C '" // trim(dir) // "' rev-parse --show-toplevel 2>/dev/null > " // & |
| 143 | + trim(temp_file), exitstat=stat, wait=.true.) |
| 144 | + if (stat /= 0) return |
| 145 | + |
| 146 | + open(newunit=unit, file=temp_file, status='old', iostat=ios) |
| 147 | + if (ios == 0) then |
| 148 | + read(unit, '(a)', iostat=ios) repo_root |
| 149 | + close(unit) |
| 150 | + end if |
| 151 | + call execute_command_line("rm -f " // trim(temp_file) // " 2>/dev/null") |
| 152 | + if (ios /= 0) return |
| 153 | + |
| 154 | + ! Calculate relative path from repo root to current directory |
| 155 | + repo_root_len = len_trim(repo_root) |
| 156 | + if (trim(dir) == trim(repo_root)) then |
| 157 | + rel_path = "" |
| 158 | + else if (len_trim(dir) > repo_root_len .and. dir(1:repo_root_len) == trim(repo_root)) then |
| 159 | + ! Remove repo_root + "/" from dir |
| 160 | + rel_path = dir(repo_root_len+2:) ! +2 to skip the "/" |
| 161 | + else |
| 162 | + rel_path = "" |
| 163 | + end if |
| 164 | + |
| 165 | + ! Get git status from repo root |
| 166 | + temp_file = trim(temp_file) // "_status" |
| 167 | + call execute_command_line("cd '" // trim(repo_root) // "' && git status --porcelain 2>/dev/null > " // & |
| 141 | 168 | trim(temp_file), exitstat=stat, wait=.true.) |
| 142 | 169 | |
| 143 | 170 | if (stat /= 0) return |
@@ -152,17 +179,43 @@ contains |
| 152 | 179 | |
| 153 | 180 | if (len_trim(line) > 3) then |
| 154 | 181 | git_status = line(1:2) |
| 155 | | - file_path = trim(adjustl(line(4:))) |
| 182 | + full_status_path = trim(adjustl(line(4:))) |
| 156 | 183 | |
| 157 | | - ! Match against our file list |
| 184 | + ! Check if this file is in our current directory or subdirectory |
| 158 | 185 | do i = 1, count |
| 159 | | - if (trim(files(i)) == trim(file_path)) then |
| 160 | | - ! Parse git status (XY format) |
| 186 | + ! Build expected path for this file |
| 187 | + if (len_trim(rel_path) > 0) then |
| 188 | + file_path = trim(rel_path) // "/" // trim(files(i)) |
| 189 | + else |
| 190 | + file_path = trim(files(i)) |
| 191 | + end if |
| 192 | + |
| 193 | + ! For files: exact match |
| 194 | + if (.not. is_dir_arr(i) .and. trim(full_status_path) == trim(file_path)) then |
| 161 | 195 | is_untracked(i) = (git_status == '??') |
| 162 | 196 | is_staged(i) = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?') |
| 163 | 197 | is_unstaged(i) = (git_status(2:2) /= ' ' .and. .not. is_untracked(i)) |
| 164 | 198 | exit |
| 165 | 199 | end if |
| 200 | + |
| 201 | + ! For directories: check if status path starts with dirname/ |
| 202 | + if (is_dir_arr(i) .and. trim(files(i)) /= "." .and. trim(files(i)) /= "..") then |
| 203 | + ! Build directory path |
| 204 | + if (len_trim(rel_path) > 0) then |
| 205 | + file_path = trim(rel_path) // "/" // trim(files(i)) // "/" |
| 206 | + else |
| 207 | + file_path = trim(files(i)) // "/" |
| 208 | + end if |
| 209 | + |
| 210 | + ! Check if status path starts with this directory |
| 211 | + if (len_trim(full_status_path) >= len_trim(file_path) .and. & |
| 212 | + full_status_path(1:len_trim(file_path)) == trim(file_path)) then |
| 213 | + ! This directory contains dirty files |
| 214 | + is_untracked(i) = is_untracked(i) .or. (git_status == '??') |
| 215 | + is_staged(i) = is_staged(i) .or. (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?') |
| 216 | + is_unstaged(i) = is_unstaged(i) .or. (git_status(2:2) /= ' ' .and. git_status /= '??') |
| 217 | + end if |
| 218 | + end if |
| 166 | 219 | end do |
| 167 | 220 | end if |
| 168 | 221 | end do |