fortrangoingonforty/fuss / dafbf58

Browse files

revert commit (w. fzf), MErge branch (w. fzf), broken history h

Authored by espadonne
SHA
dafbf586ccca1aa0fbc42983adedbd5c99a9bf61
Parents
f8cc489
Tree
6cdbe1a

3 changed files

StatusFile+-
M src/display_module.f90 1 1
M src/fuss_main.f90 58 0
M src/git_module.f90 176 0
src/display_module.f90modified
@@ -169,7 +169,7 @@ contains
169169
                      achar(27) // '[31m✗' // achar(27) // '[0m=modified ' // &
170170
                      achar(27) // '[90m✗' // achar(27) // '[0m=untracked ' // &
171171
                      achar(27) // '[34m↓' // achar(27) // '[0m=incoming'
172
-        print '(A)', 'Keys: j/k/↑/↓:nav | ←/→:nav tree | space:toggle | .:hide-dots | a:stage | u:unstage | S:stage-all | U:unstage-all | x:discard | z:stash | Z:unstash | b:switch | n:new-br | R:del-br | f:fetch | d:diff | c:view | y:cherry-pick | r:delete | l:pull | m:commit | M:amend | p:push | t:tag | s:status | q:quit'
172
+        print '(A)', 'Keys: j/k/↑/↓:nav | ←/→:nav tree | space:toggle | .:hide-dots | a:stage | u:unstage | S:stage-all | U:unstage-all | x:discard | z:stash | Z:unstash | b:switch | n:new-br | R:del-br | G:merge | f:fetch | d:diff | c:view | h:history | y:cherry-pick | v:revert | r:delete | l:pull | m:commit | M:amend | p:push | t:tag | s:status | q:quit'
173173
 
174174
         ! Don't free tree - it's owned by interactive_mode
175175
     end subroutine draw_interactive_tree
src/fuss_main.f90modified
@@ -84,13 +84,16 @@ contains
8484
         print '(A)', '        f               Fetch from remote'
8585
         print '(A)', '        d               Show diff for file'
8686
         print '(A)', '        c               View file contents (bat/less/cat)'
87
+        print '(A)', '        h               Browse commit history'
8788
         print '(A)', '        y               Cherry-pick commit from branch'
89
+        print '(A)', '        v               Revert commit (safe undo)'
8890
         print '(A)', '        x               Discard changes'
8991
         print '(A)', ''
9092
         print '(A)', '    Branches & Stash:'
9193
         print '(A)', '        b               Switch branch'
9294
         print '(A)', '        n               Create new branch'
9395
         print '(A)', '        R               Delete branch'
96
+        print '(A)', '        G               Merge branch into current'
9497
         print '(A)', '        z               Stash changes'
9598
         print '(A)', '        Z               Unstash/pop changes'
9699
         print '(A)', ''
@@ -491,6 +494,33 @@ contains
491494
                 call mark_incoming_changes(files, n_files)
492495
                 call build_item_list(files, n_files, items, n_items, tree_root, hide_dotfiles)
493496
                 if (selected > n_items .and. n_items > 0) selected = n_items
497
+            case ('v')  ! Revert commit
498
+                call revert_commit_prompt()
499
+                ! Refresh files after revert
500
+                if (show_all) then
501
+                    call get_all_files(files, n_files)
502
+                else
503
+                    call get_dirty_files(files, n_files)
504
+                end if
505
+                call mark_incoming_changes(files, n_files)
506
+                call build_item_list(files, n_files, items, n_items, tree_root, hide_dotfiles)
507
+                if (selected > n_items .and. n_items > 0) selected = n_items
508
+            case ('h')  ! Show commit history
509
+                call history_browser_prompt()
510
+                ! No refresh needed - read-only
511
+            case ('G')  ! Merge branch (Shift+g)
512
+                call merge_branch_prompt()
513
+                ! Refresh files after merge
514
+                if (show_all) then
515
+                    call get_all_files(files, n_files)
516
+                else
517
+                    call get_dirty_files(files, n_files)
518
+                end if
519
+                call mark_incoming_changes(files, n_files)
520
+                call build_item_list(files, n_files, items, n_items, tree_root, hide_dotfiles)
521
+                if (selected > n_items .and. n_items > 0) selected = n_items
522
+                ! Update branch name display in case we merged
523
+                call get_repo_info(repo_name, branch_name)
494524
             case ('.')  ! Toggle hiding dotfiles and gitignored files
495525
                 hide_dotfiles = .not. hide_dotfiles
496526
                 ! Rebuild item list with new filter
@@ -1172,6 +1202,34 @@ contains
11721202
         call git_cherry_pick(success)
11731203
     end subroutine cherry_pick_prompt
11741204
 
1205
+    subroutine revert_commit_prompt()
1206
+        logical :: success
1207
+
1208
+        ! Clear screen for revert
1209
+        call clear_screen()
1210
+
1211
+        ! Call git revert (handles its own prompts and key wait)
1212
+        call git_revert_commit(success)
1213
+    end subroutine revert_commit_prompt
1214
+
1215
+    subroutine history_browser_prompt()
1216
+        ! Clear screen for history browser
1217
+        call clear_screen()
1218
+
1219
+        ! Call git history browser (handles its own terminal setup)
1220
+        call git_show_history()
1221
+    end subroutine history_browser_prompt
1222
+
1223
+    subroutine merge_branch_prompt()
1224
+        logical :: success
1225
+
1226
+        ! Clear screen for merge
1227
+        call clear_screen()
1228
+
1229
+        ! Call git merge (handles its own prompts and key wait)
1230
+        call git_merge_branch(success)
1231
+    end subroutine merge_branch_prompt
1232
+
11751233
     subroutine branch_create_prompt()
11761234
         character(len=512) :: branch_name
11771235
         logical :: success
src/git_module.f90modified
@@ -1428,6 +1428,182 @@ contains
14281428
         call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
14291429
     end subroutine git_cherry_pick
14301430
 
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
+
14311607
     subroutine git_tag(tag_name, tag_message, success)
14321608
         character(len=*), intent(in) :: tag_name
14331609
         character(len=*), intent(in) :: tag_message