@@ -1428,6 +1428,182 @@ contains |
| 1428 | 1428 | call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1429 | 1429 | end subroutine git_cherry_pick |
| 1430 | 1430 | |
| 1431 | + subroutine git_revert_commit(success) |
| 1432 | + logical, intent(out) :: success |
| 1433 | + integer :: status_code, status |
| 1434 | + character(len=512) :: selected_commit |
| 1435 | + character(len=2048) :: command |
| 1436 | + |
| 1437 | + success = .false. |
| 1438 | + |
| 1439 | + ! Restore terminal for fzf |
| 1440 | + call execute_command_line('stty sane < /dev/tty', exitstat=status) |
| 1441 | + |
| 1442 | + print '(A)', achar(27) // '[1mRevert Commit' // achar(27) // '[0m' |
| 1443 | + print '(A)', '' |
| 1444 | + print '(A)', 'Select commit to revert (creates a new commit that undoes changes):' |
| 1445 | + print '(A)', '' |
| 1446 | + |
| 1447 | + ! Select commit from history |
| 1448 | + call execute_command_line('git log --oneline --color=always -n 100 | ' // & |
| 1449 | + 'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1450 | + '--prompt="Commit to revert: " ' // & |
| 1451 | + '--preview="git show --color=always {1}" ' // & |
| 1452 | + '--preview-window=right:60% > /tmp/fuss_revert_select.txt', & |
| 1453 | + exitstat=status_code) |
| 1454 | + |
| 1455 | + if (status_code /= 0) then |
| 1456 | + call execute_command_line('rm -f /tmp/fuss_revert_select.txt', exitstat=status) |
| 1457 | + print '(A)', '' |
| 1458 | + print '(A)', 'Operation cancelled.' |
| 1459 | + print '(A)', '' |
| 1460 | + print '(A)', 'Press any key to continue...' |
| 1461 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1462 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1463 | + return |
| 1464 | + end if |
| 1465 | + |
| 1466 | + ! Read selected commit hash |
| 1467 | + open(unit=99, file='/tmp/fuss_revert_select.txt', status='old', action='read') |
| 1468 | + read(99, '(A)', iostat=status) selected_commit |
| 1469 | + close(99, status='delete') |
| 1470 | + |
| 1471 | + if (status /= 0 .or. len_trim(selected_commit) == 0) then |
| 1472 | + print '(A)', '' |
| 1473 | + print '(A)', 'No commit selected.' |
| 1474 | + print '(A)', '' |
| 1475 | + print '(A)', 'Press any key to continue...' |
| 1476 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1477 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1478 | + return |
| 1479 | + end if |
| 1480 | + |
| 1481 | + ! Extract commit hash (first word) |
| 1482 | + selected_commit = selected_commit(1:7) |
| 1483 | + |
| 1484 | + ! Perform revert |
| 1485 | + print '(A)', '' |
| 1486 | + write(command, '(A,A,A)') 'git revert --no-edit ', trim(selected_commit), ' 2>&1' |
| 1487 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1488 | + |
| 1489 | + print '(A)', '' |
| 1490 | + if (status_code == 0) then |
| 1491 | + print '(A)', achar(27) // '[32m✓ Reverted ' // trim(selected_commit) // achar(27) // '[0m' |
| 1492 | + success = .true. |
| 1493 | + else |
| 1494 | + print '(A)', achar(27) // '[31m✗ Revert failed or has conflicts' // achar(27) // '[0m' |
| 1495 | + print '(A)', 'Resolve conflicts, then run: git revert --continue' |
| 1496 | + print '(A)', 'Or abort with: git revert --abort' |
| 1497 | + end if |
| 1498 | + |
| 1499 | + print '(A)', '' |
| 1500 | + print '(A)', 'Press any key to continue...' |
| 1501 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1502 | + |
| 1503 | + ! Re-enable cbreak mode |
| 1504 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1505 | + end subroutine git_revert_commit |
| 1506 | + |
| 1507 | + subroutine git_show_history() |
| 1508 | + integer :: status_code, status |
| 1509 | + |
| 1510 | + ! Restore terminal for fzf |
| 1511 | + call execute_command_line('stty sane < /dev/tty', exitstat=status) |
| 1512 | + |
| 1513 | + print '(A)', achar(27) // '[1mCommit History' // achar(27) // '[0m' |
| 1514 | + print '(A)', '' |
| 1515 | + print '(A)', 'Browse commit history (read-only):' |
| 1516 | + print '(A)', '' |
| 1517 | + |
| 1518 | + ! Browse commits with fzf (no action, just viewing) |
| 1519 | + call execute_command_line('git log --oneline --graph --color=always --all | ' // & |
| 1520 | + 'fzf --ansi --height=100% --border=rounded --border-label=" ESC to close " ' // & |
| 1521 | + '--prompt="Browse commits: " ' // & |
| 1522 | + '--preview="echo {} | grep -o ''^[*|\\ /]*[0-9a-f]\+'' | head -1 | ' // & |
| 1523 | + 'xargs -I % git show --color=always %" ' // & |
| 1524 | + '--preview-window=right:60% --no-select > /dev/null', & |
| 1525 | + exitstat=status_code) |
| 1526 | + |
| 1527 | + ! Re-enable cbreak mode |
| 1528 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1529 | + end subroutine git_show_history |
| 1530 | + |
| 1531 | + subroutine git_merge_branch(success) |
| 1532 | + logical, intent(out) :: success |
| 1533 | + integer :: status_code, status |
| 1534 | + character(len=512) :: selected_branch |
| 1535 | + character(len=2048) :: command |
| 1536 | + |
| 1537 | + success = .false. |
| 1538 | + |
| 1539 | + ! Restore terminal for fzf |
| 1540 | + call execute_command_line('stty sane < /dev/tty', exitstat=status) |
| 1541 | + |
| 1542 | + print '(A)', achar(27) // '[1mMerge Branch' // achar(27) // '[0m' |
| 1543 | + print '(A)', '' |
| 1544 | + print '(A)', 'Select branch to merge into current branch:' |
| 1545 | + print '(A)', '' |
| 1546 | + |
| 1547 | + ! Select branch (exclude current branch) |
| 1548 | + call execute_command_line('(git branch --all | grep -v HEAD | grep -v "^\*" | sed "s/^[* ] //" | ' // & |
| 1549 | + 'sed "s/remotes\\/origin\\///" | sort -u) | ' // & |
| 1550 | + 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1551 | + '--prompt="Branch to merge: " ' // & |
| 1552 | + '--preview="echo Commits to merge:; git log --oneline --color=always HEAD..{} | head -20" ' // & |
| 1553 | + '--preview-window=right:50% > /tmp/fuss_merge_select.txt', & |
| 1554 | + exitstat=status_code) |
| 1555 | + |
| 1556 | + if (status_code /= 0) then |
| 1557 | + call execute_command_line('rm -f /tmp/fuss_merge_select.txt', exitstat=status) |
| 1558 | + print '(A)', '' |
| 1559 | + print '(A)', 'Operation cancelled.' |
| 1560 | + print '(A)', '' |
| 1561 | + print '(A)', 'Press any key to continue...' |
| 1562 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1563 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1564 | + return |
| 1565 | + end if |
| 1566 | + |
| 1567 | + ! Read selected branch |
| 1568 | + open(unit=99, file='/tmp/fuss_merge_select.txt', status='old', action='read') |
| 1569 | + read(99, '(A)', iostat=status) selected_branch |
| 1570 | + close(99, status='delete') |
| 1571 | + |
| 1572 | + if (status /= 0 .or. len_trim(selected_branch) == 0) then |
| 1573 | + print '(A)', '' |
| 1574 | + print '(A)', 'No branch selected.' |
| 1575 | + print '(A)', '' |
| 1576 | + print '(A)', 'Press any key to continue...' |
| 1577 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1578 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1579 | + return |
| 1580 | + end if |
| 1581 | + |
| 1582 | + ! Perform merge |
| 1583 | + print '(A)', '' |
| 1584 | + print '(A)', 'Merging ' // trim(selected_branch) // ' into current branch...' |
| 1585 | + print '(A)', '' |
| 1586 | + write(command, '(A,A,A)') 'git merge ', trim(selected_branch), ' 2>&1' |
| 1587 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1588 | + |
| 1589 | + print '(A)', '' |
| 1590 | + if (status_code == 0) then |
| 1591 | + print '(A)', achar(27) // '[32m✓ Merged ' // trim(selected_branch) // achar(27) // '[0m' |
| 1592 | + success = .true. |
| 1593 | + else |
| 1594 | + print '(A)', achar(27) // '[31m✗ Merge failed or has conflicts' // achar(27) // '[0m' |
| 1595 | + print '(A)', 'Resolve conflicts, then run: git merge --continue' |
| 1596 | + print '(A)', 'Or abort with: git merge --abort' |
| 1597 | + end if |
| 1598 | + |
| 1599 | + print '(A)', '' |
| 1600 | + print '(A)', 'Press any key to continue...' |
| 1601 | + call execute_command_line('read -n 1 -s < /dev/tty', exitstat=status) |
| 1602 | + |
| 1603 | + ! Re-enable cbreak mode |
| 1604 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
| 1605 | + end subroutine git_merge_branch |
| 1606 | + |
| 1431 | 1607 | subroutine git_tag(tag_name, tag_message, success) |
| 1432 | 1608 | character(len=*), intent(in) :: tag_name |
| 1433 | 1609 | character(len=*), intent(in) :: tag_message |