fortrangoingonforty/fuss / 95a704e

Browse files

scratch

Authored by espadonne
SHA
95a704e5aab2b3fb2bf3a26bc35bed218c6988be
Parents
7139e59
Tree
8e0fa6d

1 changed file

StatusFile+-
M fuss.f90 179 11
fuss.f90modified
@@ -9,6 +9,7 @@ program fuss
99
         logical :: is_staged
1010
         logical :: is_unstaged
1111
         logical :: is_untracked
12
+        logical :: has_incoming
1213
         type(tree_node), pointer :: first_child => null()
1314
         type(tree_node), pointer :: next_sibling => null()
1415
     end type tree_node
@@ -19,6 +20,7 @@ program fuss
1920
         logical :: is_staged
2021
         logical :: is_unstaged
2122
         logical :: is_untracked
23
+        logical :: has_incoming
2224
     end type file_entry
2325
 
2426
     type :: selectable_item
@@ -26,6 +28,7 @@ program fuss
2628
         logical :: is_staged
2729
         logical :: is_unstaged
2830
         logical :: is_untracked
31
+        logical :: has_incoming
2932
         logical :: is_file
3033
     end type selectable_item
3134
 
@@ -94,6 +97,9 @@ contains
9497
             call get_dirty_files(files, n_files)
9598
         end if
9699
 
100
+        ! Mark files with incoming changes
101
+        call mark_incoming_changes(files, n_files)
102
+
97103
         ! Display the tree
98104
         if (n_files > 0) then
99105
             print '(A)', '.'
@@ -167,6 +173,7 @@ contains
167173
                 temp_files(n_files)%is_untracked = (git_status == '??')
168174
                 temp_files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
169175
                 temp_files(n_files)%is_unstaged = (git_status(2:2) /= ' ' .and. .not. temp_files(n_files)%is_untracked)
176
+                temp_files(n_files)%has_incoming = .false.
170177
             end if
171178
         end do
172179
 
@@ -242,6 +249,7 @@ contains
242249
                 temp_files(n_files)%is_staged = .false.
243250
                 temp_files(n_files)%is_unstaged = .false.
244251
                 temp_files(n_files)%is_untracked = .false.
252
+                temp_files(n_files)%has_incoming = .false.
245253
                 do i = 1, n_dirty
246254
                     if (trim(dirty_files(i)%path) == trim(line)) then
247255
                         is_dirty_file = .true.
@@ -249,6 +257,7 @@ contains
249257
                         temp_files(n_files)%is_staged = dirty_files(i)%is_staged
250258
                         temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged
251259
                         temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked
260
+                        temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming
252261
                         exit
253262
                     end if
254263
                 end do
@@ -265,6 +274,51 @@ contains
265274
         if (allocated(dirty_files)) deallocate(dirty_files)
266275
     end subroutine get_all_files
267276
 
277
+    subroutine mark_incoming_changes(files, n_files)
278
+        type(file_entry), intent(inout) :: files(:)
279
+        integer, intent(in) :: n_files
280
+        integer :: iostat, unit_num, status_code, i
281
+        character(len=1024) :: line
282
+        character(len=512) :: incoming_path
283
+
284
+        ! Check if there's an upstream branch configured
285
+        call execute_command_line('git rev-parse --abbrev-ref @{upstream} > /dev/null 2>&1', exitstat=status_code)
286
+        if (status_code /= 0) then
287
+            ! No upstream configured, no incoming changes possible
288
+            return
289
+        end if
290
+
291
+        ! Get list of files that differ between HEAD and upstream
292
+        call execute_command_line('git diff --name-only HEAD...@{upstream} > /tmp/fuss_incoming.txt 2>/dev/null', &
293
+                                  exitstat=status_code)
294
+
295
+        if (status_code /= 0) then
296
+            ! If diff fails, no incoming changes
297
+            return
298
+        end if
299
+
300
+        open(newunit=unit_num, file='/tmp/fuss_incoming.txt', status='old', action='read', iostat=iostat)
301
+        if (iostat /= 0) return
302
+
303
+        do
304
+            read(unit_num, '(A)', iostat=iostat) line
305
+            if (iostat /= 0) exit
306
+
307
+            if (len_trim(line) > 0) then
308
+                incoming_path = trim(line)
309
+                ! Mark this file as having incoming changes
310
+                do i = 1, n_files
311
+                    if (trim(files(i)%path) == trim(incoming_path)) then
312
+                        files(i)%has_incoming = .true.
313
+                        exit
314
+                    end if
315
+                end do
316
+            end if
317
+        end do
318
+
319
+        close(unit_num, status='delete')
320
+    end subroutine mark_incoming_changes
321
+
268322
     subroutine resize_array(array, new_size)
269323
         type(file_entry), allocatable, intent(inout) :: array(:)
270324
         integer, intent(in) :: new_size
@@ -323,6 +377,7 @@ contains
323377
                 files(n_files)%is_untracked = (git_status == '??')
324378
                 files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
325379
                 files(n_files)%is_unstaged = (git_status(2:2) /= ' ' .and. .not. files(n_files)%is_untracked)
380
+                files(n_files)%has_incoming = .false.
326381
             end if
327382
         end do
328383
 
@@ -342,12 +397,13 @@ contains
342397
         root%is_staged = .false.
343398
         root%is_unstaged = .false.
344399
         root%is_untracked = .false.
400
+        root%has_incoming = .false.
345401
         root%first_child => null()
346402
         root%next_sibling => null()
347403
 
348404
         ! Build tree
349405
         do i = 1, n_files
350
-            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked)
406
+            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked, files(i)%has_incoming)
351407
         end do
352408
 
353409
         ! Sort tree (directories first, then alphabetically)
@@ -375,6 +431,9 @@ contains
375431
             call get_dirty_files(files, n_files)
376432
         end if
377433
 
434
+        ! Mark files with incoming changes
435
+        call mark_incoming_changes(files, n_files)
436
+
378437
         if (n_files == 0) then
379438
             print '(A)', 'No files to display'
380439
             return
@@ -414,10 +473,37 @@ contains
414473
                     else
415474
                         call get_dirty_files(files, n_files)
416475
                     end if
476
+                    call mark_incoming_changes(files, n_files)
417477
                     call build_item_list(files, n_files, items, n_items)
418478
                     if (selected > n_items .and. n_items > 0) selected = n_items
419479
                     if (n_items == 0) running = .false.
420480
                 end if
481
+            case ('f', 'F')  ! Git fetch
482
+                call git_fetch()
483
+                ! Refresh files after fetch to update incoming indicators
484
+                if (show_all) then
485
+                    call get_all_files(files, n_files)
486
+                else
487
+                    call get_dirty_files(files, n_files)
488
+                end if
489
+                call mark_incoming_changes(files, n_files)
490
+                call build_item_list(files, n_files, items, n_items)
491
+                if (selected > n_items .and. n_items > 0) selected = n_items
492
+            case ('d', 'D')  ! Git diff with less
493
+                if (items(selected)%is_file) then
494
+                    call git_diff_file(items(selected)%path)
495
+                end if
496
+            case ('l', 'L')  ! Git pull
497
+                call git_pull()
498
+                ! Refresh files after pull
499
+                if (show_all) then
500
+                    call get_all_files(files, n_files)
501
+                else
502
+                    call get_dirty_files(files, n_files)
503
+                end if
504
+                call mark_incoming_changes(files, n_files)
505
+                call build_item_list(files, n_files, items, n_items)
506
+                if (selected > n_items .and. n_items > 0) selected = n_items
421507
             case ('q', 'Q')  ! Quit
422508
                 running = .false.
423509
             end select
@@ -447,11 +533,12 @@ contains
447533
         root%is_staged = .false.
448534
         root%is_unstaged = .false.
449535
         root%is_untracked = .false.
536
+        root%has_incoming = .false.
450537
         root%first_child => null()
451538
         root%next_sibling => null()
452539
 
453540
         do i = 1, n_files
454
-            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked)
541
+            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked, files(i)%has_incoming)
455542
         end do
456543
 
457544
         call sort_tree(root)
@@ -501,6 +588,7 @@ contains
501588
             items(n_items)%is_staged = node%is_staged
502589
             items(n_items)%is_unstaged = node%is_unstaged
503590
             items(n_items)%is_untracked = node%is_untracked
591
+            items(n_items)%has_incoming = node%has_incoming
504592
         else
505593
             full_path = ''
506594
         end if
@@ -591,6 +679,68 @@ contains
591679
         call execute_command_line('sleep 0.5', exitstat=status)
592680
     end subroutine git_add_file
593681
 
682
+    subroutine git_fetch()
683
+        integer :: status
684
+
685
+        ! Restore terminal temporarily for git output
686
+        call disable_raw_mode()
687
+
688
+        ! Run git fetch
689
+        print '(A)', 'Fetching from remote...'
690
+        call execute_command_line('git fetch', exitstat=status)
691
+
692
+        if (status == 0) then
693
+            print '(A)', 'Fetch completed successfully!'
694
+        else
695
+            print '(A)', 'Fetch failed!'
696
+        end if
697
+
698
+        ! Brief pause to show message
699
+        call execute_command_line('sleep 1', exitstat=status)
700
+
701
+        ! Re-enable raw mode
702
+        call enable_raw_mode()
703
+    end subroutine git_fetch
704
+
705
+    subroutine git_pull()
706
+        integer :: status
707
+
708
+        ! Restore terminal temporarily for git output
709
+        call disable_raw_mode()
710
+
711
+        ! Run git pull
712
+        print '(A)', 'Pulling from remote...'
713
+        call execute_command_line('git pull', exitstat=status)
714
+
715
+        if (status == 0) then
716
+            print '(A)', 'Pull completed successfully!'
717
+        else
718
+            print '(A)', 'Pull failed!'
719
+        end if
720
+
721
+        ! Brief pause to show message
722
+        call execute_command_line('sleep 1', exitstat=status)
723
+
724
+        ! Re-enable raw mode
725
+        call enable_raw_mode()
726
+    end subroutine git_pull
727
+
728
+    subroutine git_diff_file(filepath)
729
+        character(len=*), intent(in) :: filepath
730
+        character(len=1024) :: command
731
+        integer :: status
732
+
733
+        ! Restore terminal temporarily for less
734
+        call disable_raw_mode()
735
+
736
+        ! Show diff with less
737
+        write(command, '(A,A,A)') 'git diff HEAD...@{upstream} -- "', trim(filepath), '" | less -R'
738
+        call execute_command_line(trim(command), exitstat=status)
739
+
740
+        ! Re-enable raw mode
741
+        call enable_raw_mode()
742
+    end subroutine git_diff_file
743
+
594744
     subroutine draw_interactive_tree(files, n_files, items, n_items, selected)
595745
         type(file_entry), intent(in) :: files(:)
596746
         integer, intent(in) :: n_files, n_items, selected
@@ -605,11 +755,12 @@ contains
605755
         root%is_staged = .false.
606756
         root%is_unstaged = .false.
607757
         root%is_untracked = .false.
758
+        root%has_incoming = .false.
608759
         root%first_child => null()
609760
         root%next_sibling => null()
610761
 
611762
         do i = 1, n_files
612
-            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked)
763
+            call add_to_tree(root, files(i)%path, files(i)%is_staged, files(i)%is_unstaged, files(i)%is_untracked, files(i)%has_incoming)
613764
         end do
614765
 
615766
         call sort_tree(root)
@@ -620,12 +771,13 @@ contains
620771
         call print_interactive_node(root, '', .true., .true., items, &
621772
                                    selected, item_idx)
622773
 
623
-        ! Print help
774
+        ! Print help (two rows for better readability)
624775
         print '(A)', ''
625
-        print '(A)', achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // &
776
+        print '(A)', 'Legend: ' // achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // &
626777
                      achar(27) // '[31m✗' // achar(27) // '[0m=modified ' // &
627
-                     achar(27) // '[90m✗' // achar(27) // '[0m=untracked'
628
-        print '(A)', 'j/↓: down | k/↑: up | Space: stage file | q: quit'
778
+                     achar(27) // '[90m✗' // achar(27) // '[0m=untracked ' // &
779
+                     achar(27) // '[34m↓' // achar(27) // '[0m=incoming'
780
+        print '(A)', 'Keys: j/k/↑/↓:nav | Space:stage | f:fetch | d:diff | l:pull | q:quit'
629781
 
630782
         call free_tree(root)
631783
     end subroutine draw_interactive_tree
@@ -656,11 +808,13 @@ contains
656808
         character(len=50) :: mark_unstaged
657809
         character(len=50) :: mark_untracked
658810
         character(len=50) :: mark_staged
811
+        character(len=50) :: mark_incoming
659812
 
660813
         ! Initialize colored marks with explicit ESC characters
661814
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
662815
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
663816
         write(mark_staged, '(A,A,A,A,A)') ESC, '[32m', ' ↑', ESC, '[0m'  ! Green for staged
817
+        write(mark_incoming, '(A,A,A,A,A)') ESC, '[34m', ' ↓', ESC, '[0m'  ! Blue for incoming
664818
 
665819
         ! Count children first
666820
         n_children = 0
@@ -696,6 +850,9 @@ contains
696850
                 if (node%is_untracked) then
697851
                     line = trim(line) // trim(mark_untracked)
698852
                 end if
853
+                if (node%has_incoming) then
854
+                    line = trim(line) // trim(mark_incoming)
855
+                end if
699856
                 line = trim(line) // highlight_off
700857
             else
701858
                 line = trim(line) // trim(node%name)
@@ -709,6 +866,9 @@ contains
709866
                 if (node%is_untracked) then
710867
                     line = trim(line) // trim(mark_untracked)
711868
                 end if
869
+                if (node%has_incoming) then
870
+                    line = trim(line) // trim(mark_incoming)
871
+                end if
712872
             end if
713873
 
714874
             print '(A)', trim(line)
@@ -820,10 +980,10 @@ contains
820980
         before = (trim(a%name) < trim(b%name))
821981
     end function should_insert_before
822982
 
823
-    recursive subroutine add_to_tree(node, path, is_staged, is_unstaged, is_untracked)
983
+    recursive subroutine add_to_tree(node, path, is_staged, is_unstaged, is_untracked, has_incoming)
824984
         type(tree_node), pointer, intent(in) :: node
825985
         character(len=*), intent(in) :: path
826
-        logical, intent(in) :: is_staged, is_unstaged, is_untracked
986
+        logical, intent(in) :: is_staged, is_unstaged, is_untracked, has_incoming
827987
 
828988
         integer :: slash_pos, iostat
829989
         character(len=512) :: first_part, rest
@@ -843,6 +1003,7 @@ contains
8431003
                     child%is_staged = child%is_staged .or. is_staged
8441004
                     child%is_unstaged = child%is_unstaged .or. is_unstaged
8451005
                     child%is_untracked = child%is_untracked .or. is_untracked
1006
+                    child%has_incoming = child%has_incoming .or. has_incoming
8461007
                     return
8471008
                 end if
8481009
                 if (.not. associated(child%next_sibling)) exit
@@ -869,6 +1030,7 @@ contains
8691030
             new_child%is_staged = is_staged
8701031
             new_child%is_unstaged = is_unstaged
8711032
             new_child%is_untracked = is_untracked
1033
+            new_child%has_incoming = has_incoming
8721034
             new_child%first_child => null()
8731035
             new_child%next_sibling => null()
8741036
 
@@ -886,7 +1048,7 @@ contains
8861048
             child => node%first_child
8871049
             do while (associated(child))
8881050
                 if (trim(child%name) == trim(first_part)) then
889
-                    call add_to_tree(child, rest, is_staged, is_unstaged, is_untracked)
1051
+                    call add_to_tree(child, rest, is_staged, is_unstaged, is_untracked, has_incoming)
8901052
                     return
8911053
                 end if
8921054
                 if (.not. associated(child%next_sibling)) exit
@@ -900,6 +1062,7 @@ contains
9001062
             new_child%is_staged = .false.
9011063
             new_child%is_unstaged = .false.
9021064
             new_child%is_untracked = .false.
1065
+            new_child%has_incoming = .false.
9031066
             new_child%first_child => null()
9041067
             new_child%next_sibling => null()
9051068
 
@@ -909,7 +1072,7 @@ contains
9091072
                 child%next_sibling => new_child
9101073
             end if
9111074
 
912
-            call add_to_tree(new_child, rest, is_staged, is_unstaged, is_untracked)
1075
+            call add_to_tree(new_child, rest, is_staged, is_unstaged, is_untracked, has_incoming)
9131076
         end if
9141077
     end subroutine add_to_tree
9151078
 
@@ -933,11 +1096,13 @@ contains
9331096
         character(len=50) :: mark_unstaged
9341097
         character(len=50) :: mark_untracked
9351098
         character(len=50) :: mark_staged
1099
+        character(len=50) :: mark_incoming
9361100
 
9371101
         ! Initialize colored marks with explicit ESC characters
9381102
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
9391103
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
9401104
         write(mark_staged, '(A,A,A,A,A)') ESC, '[32m', ' ↑', ESC, '[0m'  ! Green for staged
1105
+        write(mark_incoming, '(A,A,A,A,A)') ESC, '[34m', ' ↓', ESC, '[0m'  ! Blue for incoming
9411106
 
9421107
         ! Count children first
9431108
         n_children = 0
@@ -965,6 +1130,9 @@ contains
9651130
             if (node%is_untracked) then
9661131
                 line = trim(line) // trim(mark_untracked)
9671132
             end if
1133
+            if (node%has_incoming) then
1134
+                line = trim(line) // trim(mark_incoming)
1135
+            end if
9681136
             print '(A)', trim(line)
9691137
         end if
9701138