@@ -1274,6 +1274,160 @@ contains |
| 1274 | 1274 | call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1275 | 1275 | end subroutine git_diff_file |
| 1276 | 1276 | |
| 1277 | + subroutine view_file(filepath) |
| 1278 | + character(len=*), intent(in) :: filepath |
| 1279 | + character(len=2048) :: command |
| 1280 | + integer :: status |
| 1281 | + logical :: bat_available, less_available |
| 1282 | + |
| 1283 | + ! Restore terminal temporarily for pager |
| 1284 | + call execute_command_line('stty sane < /dev/tty', exitstat=status) |
| 1285 | + |
| 1286 | + ! Check if bat is available |
| 1287 | + call execute_command_line('command -v bat > /dev/null 2>&1', exitstat=status) |
| 1288 | + bat_available = (status == 0) |
| 1289 | + |
| 1290 | + ! Check if less is available |
| 1291 | + call execute_command_line('command -v less > /dev/null 2>&1', exitstat=status) |
| 1292 | + less_available = (status == 0) |
| 1293 | + |
| 1294 | + ! Use bat if available (with nice syntax highlighting) |
| 1295 | + if (bat_available) then |
| 1296 | + write(command, '(A,A,A)') 'bat --style=numbers,changes --color=always "', trim(filepath), '"' |
| 1297 | + call execute_command_line(trim(command), exitstat=status) |
| 1298 | + else if (less_available) then |
| 1299 | + ! Fallback to less |
| 1300 | + write(command, '(A,A,A)') 'less "', trim(filepath), '"' |
| 1301 | + call execute_command_line(trim(command), exitstat=status) |
| 1302 | + else |
| 1303 | + ! Final fallback to cat with line numbers |
| 1304 | + write(command, '(A,A,A)') 'cat -n "', trim(filepath), '"' |
| 1305 | + call execute_command_line(trim(command), exitstat=status) |
| 1306 | + ! Pause so user can read |
| 1307 | + print '(A)', '' |
| 1308 | + print '(A)', 'Press any key to continue...' |
| 1309 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1310 | + end if |
| 1311 | + |
| 1312 | + ! Re-enable cbreak mode |
| 1313 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1314 | + end subroutine view_file |
| 1315 | + |
| 1316 | + subroutine git_cherry_pick(success) |
| 1317 | + logical, intent(out) :: success |
| 1318 | + integer :: status_code, status |
| 1319 | + character(len=512) :: selected_branch, selected_commit |
| 1320 | + character(len=2048) :: command |
| 1321 | + |
| 1322 | + success = .false. |
| 1323 | + |
| 1324 | + ! Restore terminal for fzf |
| 1325 | + call execute_command_line('stty sane < /dev/tty', exitstat=status) |
| 1326 | + |
| 1327 | + print '(A)', achar(27) // '[1mCherry-pick' // achar(27) // '[0m' |
| 1328 | + print '(A)', '' |
| 1329 | + print '(A)', 'Select source branch:' |
| 1330 | + print '(A)', '' |
| 1331 | + |
| 1332 | + ! Step 1: Select branch (exclude current branch) |
| 1333 | + call execute_command_line('(git branch --all | grep -v HEAD | grep -v "^\*" | sed "s/^[* ] //" | ' // & |
| 1334 | + 'sed "s/remotes\\/origin\\///" | sort -u) | ' // & |
| 1335 | + 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1336 | + '--prompt="Source branch: " ' // & |
| 1337 | + '--preview="git log --oneline --graph --color=always {} | head -20" ' // & |
| 1338 | + '--preview-window=right:50% > /tmp/fuss_branch_select.txt', & |
| 1339 | + exitstat=status_code) |
| 1340 | + |
| 1341 | + if (status_code /= 0) then |
| 1342 | + call execute_command_line('rm -f /tmp/fuss_branch_select.txt', exitstat=status) |
| 1343 | + print '(A)', '' |
| 1344 | + print '(A)', 'Operation cancelled.' |
| 1345 | + print '(A)', '' |
| 1346 | + print '(A)', 'Press any key to continue...' |
| 1347 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1348 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1349 | + return |
| 1350 | + end if |
| 1351 | + |
| 1352 | + ! Read selected branch |
| 1353 | + open(unit=99, file='/tmp/fuss_branch_select.txt', status='old', action='read') |
| 1354 | + read(99, '(A)', iostat=status) selected_branch |
| 1355 | + close(99, status='delete') |
| 1356 | + |
| 1357 | + if (status /= 0 .or. len_trim(selected_branch) == 0) then |
| 1358 | + print '(A)', '' |
| 1359 | + print '(A)', 'No branch selected.' |
| 1360 | + print '(A)', '' |
| 1361 | + print '(A)', 'Press any key to continue...' |
| 1362 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1363 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1364 | + return |
| 1365 | + end if |
| 1366 | + |
| 1367 | + ! Step 2: Select commit from that branch (only commits NOT in current branch) |
| 1368 | + print '(A)', '' |
| 1369 | + print '(A)', 'Select commit to cherry-pick from: ' // trim(selected_branch) |
| 1370 | + print '(A)', '' |
| 1371 | + |
| 1372 | + write(command, '(A,A,A)') '(git log ', trim(selected_branch), ' --not HEAD --oneline --color=always) | ' // & |
| 1373 | + 'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1374 | + '--prompt="Commit: " ' // & |
| 1375 | + '--preview="git show --color=always {1}" ' // & |
| 1376 | + '--preview-window=right:60% > /tmp/fuss_commit_select.txt' |
| 1377 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1378 | + |
| 1379 | + if (status_code /= 0) then |
| 1380 | + call execute_command_line('rm -f /tmp/fuss_commit_select.txt', exitstat=status) |
| 1381 | + print '(A)', '' |
| 1382 | + print '(A)', 'Operation cancelled.' |
| 1383 | + print '(A)', '' |
| 1384 | + print '(A)', 'Press any key to continue...' |
| 1385 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1386 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1387 | + return |
| 1388 | + end if |
| 1389 | + |
| 1390 | + ! Read selected commit (first 7 chars is the hash) |
| 1391 | + open(unit=99, file='/tmp/fuss_commit_select.txt', status='old', action='read') |
| 1392 | + read(99, '(A)', iostat=status) selected_commit |
| 1393 | + close(99, status='delete') |
| 1394 | + |
| 1395 | + if (status /= 0 .or. len_trim(selected_commit) == 0) then |
| 1396 | + print '(A)', '' |
| 1397 | + print '(A)', 'No commit selected.' |
| 1398 | + print '(A)', '' |
| 1399 | + print '(A)', 'Press any key to continue...' |
| 1400 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1401 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1402 | + return |
| 1403 | + end if |
| 1404 | + |
| 1405 | + ! Extract commit hash (first word) |
| 1406 | + selected_commit = selected_commit(1:7) |
| 1407 | + |
| 1408 | + ! Step 3: Perform cherry-pick |
| 1409 | + print '(A)', '' |
| 1410 | + write(command, '(A,A,A)') 'git cherry-pick ', trim(selected_commit), ' 2>&1' |
| 1411 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1412 | + |
| 1413 | + print '(A)', '' |
| 1414 | + if (status_code == 0) then |
| 1415 | + print '(A)', achar(27) // '[32m✓ Cherry-picked ' // trim(selected_commit) // achar(27) // '[0m' |
| 1416 | + success = .true. |
| 1417 | + else |
| 1418 | + print '(A)', achar(27) // '[31m✗ Cherry-pick failed or has conflicts' // achar(27) // '[0m' |
| 1419 | + print '(A)', 'Resolve conflicts, then run: git cherry-pick --continue' |
| 1420 | + print '(A)', 'Or abort with: git cherry-pick --abort' |
| 1421 | + end if |
| 1422 | + |
| 1423 | + print '(A)', '' |
| 1424 | + print '(A)', 'Press any key to continue...' |
| 1425 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1426 | + |
| 1427 | + ! Re-enable cbreak mode |
| 1428 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1429 | + end subroutine git_cherry_pick |
| 1430 | + |
| 1277 | 1431 | subroutine git_tag(tag_name, tag_message, success) |
| 1278 | 1432 | character(len=*), intent(in) :: tag_name |
| 1279 | 1433 | character(len=*), intent(in) :: tag_message |