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
9
         logical :: is_staged
9
         logical :: is_staged
10
         logical :: is_unstaged
10
         logical :: is_unstaged
11
         logical :: is_untracked
11
         logical :: is_untracked
12
+        logical :: has_incoming
12
         type(tree_node), pointer :: first_child => null()
13
         type(tree_node), pointer :: first_child => null()
13
         type(tree_node), pointer :: next_sibling => null()
14
         type(tree_node), pointer :: next_sibling => null()
14
     end type tree_node
15
     end type tree_node
@@ -19,6 +20,7 @@ program fuss
19
         logical :: is_staged
20
         logical :: is_staged
20
         logical :: is_unstaged
21
         logical :: is_unstaged
21
         logical :: is_untracked
22
         logical :: is_untracked
23
+        logical :: has_incoming
22
     end type file_entry
24
     end type file_entry
23
 
25
 
24
     type :: selectable_item
26
     type :: selectable_item
@@ -26,6 +28,7 @@ program fuss
26
         logical :: is_staged
28
         logical :: is_staged
27
         logical :: is_unstaged
29
         logical :: is_unstaged
28
         logical :: is_untracked
30
         logical :: is_untracked
31
+        logical :: has_incoming
29
         logical :: is_file
32
         logical :: is_file
30
     end type selectable_item
33
     end type selectable_item
31
 
34
 
@@ -94,6 +97,9 @@ contains
94
             call get_dirty_files(files, n_files)
97
             call get_dirty_files(files, n_files)
95
         end if
98
         end if
96
 
99
 
100
+        ! Mark files with incoming changes
101
+        call mark_incoming_changes(files, n_files)
102
+
97
         ! Display the tree
103
         ! Display the tree
98
         if (n_files > 0) then
104
         if (n_files > 0) then
99
             print '(A)', '.'
105
             print '(A)', '.'
@@ -167,6 +173,7 @@ contains
167
                 temp_files(n_files)%is_untracked = (git_status == '??')
173
                 temp_files(n_files)%is_untracked = (git_status == '??')
168
                 temp_files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
174
                 temp_files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
169
                 temp_files(n_files)%is_unstaged = (git_status(2:2) /= ' ' .and. .not. temp_files(n_files)%is_untracked)
175
                 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.
170
             end if
177
             end if
171
         end do
178
         end do
172
 
179
 
@@ -242,6 +249,7 @@ contains
242
                 temp_files(n_files)%is_staged = .false.
249
                 temp_files(n_files)%is_staged = .false.
243
                 temp_files(n_files)%is_unstaged = .false.
250
                 temp_files(n_files)%is_unstaged = .false.
244
                 temp_files(n_files)%is_untracked = .false.
251
                 temp_files(n_files)%is_untracked = .false.
252
+                temp_files(n_files)%has_incoming = .false.
245
                 do i = 1, n_dirty
253
                 do i = 1, n_dirty
246
                     if (trim(dirty_files(i)%path) == trim(line)) then
254
                     if (trim(dirty_files(i)%path) == trim(line)) then
247
                         is_dirty_file = .true.
255
                         is_dirty_file = .true.
@@ -249,6 +257,7 @@ contains
249
                         temp_files(n_files)%is_staged = dirty_files(i)%is_staged
257
                         temp_files(n_files)%is_staged = dirty_files(i)%is_staged
250
                         temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged
258
                         temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged
251
                         temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked
259
                         temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked
260
+                        temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming
252
                         exit
261
                         exit
253
                     end if
262
                     end if
254
                 end do
263
                 end do
@@ -265,6 +274,51 @@ contains
265
         if (allocated(dirty_files)) deallocate(dirty_files)
274
         if (allocated(dirty_files)) deallocate(dirty_files)
266
     end subroutine get_all_files
275
     end subroutine get_all_files
267
 
276
 
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
+
268
     subroutine resize_array(array, new_size)
322
     subroutine resize_array(array, new_size)
269
         type(file_entry), allocatable, intent(inout) :: array(:)
323
         type(file_entry), allocatable, intent(inout) :: array(:)
270
         integer, intent(in) :: new_size
324
         integer, intent(in) :: new_size
@@ -323,6 +377,7 @@ contains
323
                 files(n_files)%is_untracked = (git_status == '??')
377
                 files(n_files)%is_untracked = (git_status == '??')
324
                 files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
378
                 files(n_files)%is_staged = (git_status(1:1) /= ' ' .and. git_status(1:1) /= '?')
325
                 files(n_files)%is_unstaged = (git_status(2:2) /= ' ' .and. .not. files(n_files)%is_untracked)
379
                 files(n_files)%is_unstaged = (git_status(2:2) /= ' ' .and. .not. files(n_files)%is_untracked)
380
+                files(n_files)%has_incoming = .false.
326
             end if
381
             end if
327
         end do
382
         end do
328
 
383
 
@@ -342,12 +397,13 @@ contains
342
         root%is_staged = .false.
397
         root%is_staged = .false.
343
         root%is_unstaged = .false.
398
         root%is_unstaged = .false.
344
         root%is_untracked = .false.
399
         root%is_untracked = .false.
400
+        root%has_incoming = .false.
345
         root%first_child => null()
401
         root%first_child => null()
346
         root%next_sibling => null()
402
         root%next_sibling => null()
347
 
403
 
348
         ! Build tree
404
         ! Build tree
349
         do i = 1, n_files
405
         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)
351
         end do
407
         end do
352
 
408
 
353
         ! Sort tree (directories first, then alphabetically)
409
         ! Sort tree (directories first, then alphabetically)
@@ -375,6 +431,9 @@ contains
375
             call get_dirty_files(files, n_files)
431
             call get_dirty_files(files, n_files)
376
         end if
432
         end if
377
 
433
 
434
+        ! Mark files with incoming changes
435
+        call mark_incoming_changes(files, n_files)
436
+
378
         if (n_files == 0) then
437
         if (n_files == 0) then
379
             print '(A)', 'No files to display'
438
             print '(A)', 'No files to display'
380
             return
439
             return
@@ -414,10 +473,37 @@ contains
414
                     else
473
                     else
415
                         call get_dirty_files(files, n_files)
474
                         call get_dirty_files(files, n_files)
416
                     end if
475
                     end if
476
+                    call mark_incoming_changes(files, n_files)
417
                     call build_item_list(files, n_files, items, n_items)
477
                     call build_item_list(files, n_files, items, n_items)
418
                     if (selected > n_items .and. n_items > 0) selected = n_items
478
                     if (selected > n_items .and. n_items > 0) selected = n_items
419
                     if (n_items == 0) running = .false.
479
                     if (n_items == 0) running = .false.
420
                 end if
480
                 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
421
             case ('q', 'Q')  ! Quit
507
             case ('q', 'Q')  ! Quit
422
                 running = .false.
508
                 running = .false.
423
             end select
509
             end select
@@ -447,11 +533,12 @@ contains
447
         root%is_staged = .false.
533
         root%is_staged = .false.
448
         root%is_unstaged = .false.
534
         root%is_unstaged = .false.
449
         root%is_untracked = .false.
535
         root%is_untracked = .false.
536
+        root%has_incoming = .false.
450
         root%first_child => null()
537
         root%first_child => null()
451
         root%next_sibling => null()
538
         root%next_sibling => null()
452
 
539
 
453
         do i = 1, n_files
540
         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)
455
         end do
542
         end do
456
 
543
 
457
         call sort_tree(root)
544
         call sort_tree(root)
@@ -501,6 +588,7 @@ contains
501
             items(n_items)%is_staged = node%is_staged
588
             items(n_items)%is_staged = node%is_staged
502
             items(n_items)%is_unstaged = node%is_unstaged
589
             items(n_items)%is_unstaged = node%is_unstaged
503
             items(n_items)%is_untracked = node%is_untracked
590
             items(n_items)%is_untracked = node%is_untracked
591
+            items(n_items)%has_incoming = node%has_incoming
504
         else
592
         else
505
             full_path = ''
593
             full_path = ''
506
         end if
594
         end if
@@ -591,6 +679,68 @@ contains
591
         call execute_command_line('sleep 0.5', exitstat=status)
679
         call execute_command_line('sleep 0.5', exitstat=status)
592
     end subroutine git_add_file
680
     end subroutine git_add_file
593
 
681
 
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
+
594
     subroutine draw_interactive_tree(files, n_files, items, n_items, selected)
744
     subroutine draw_interactive_tree(files, n_files, items, n_items, selected)
595
         type(file_entry), intent(in) :: files(:)
745
         type(file_entry), intent(in) :: files(:)
596
         integer, intent(in) :: n_files, n_items, selected
746
         integer, intent(in) :: n_files, n_items, selected
@@ -605,11 +755,12 @@ contains
605
         root%is_staged = .false.
755
         root%is_staged = .false.
606
         root%is_unstaged = .false.
756
         root%is_unstaged = .false.
607
         root%is_untracked = .false.
757
         root%is_untracked = .false.
758
+        root%has_incoming = .false.
608
         root%first_child => null()
759
         root%first_child => null()
609
         root%next_sibling => null()
760
         root%next_sibling => null()
610
 
761
 
611
         do i = 1, n_files
762
         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)
613
         end do
764
         end do
614
 
765
 
615
         call sort_tree(root)
766
         call sort_tree(root)
@@ -620,12 +771,13 @@ contains
620
         call print_interactive_node(root, '', .true., .true., items, &
771
         call print_interactive_node(root, '', .true., .true., items, &
621
                                    selected, item_idx)
772
                                    selected, item_idx)
622
 
773
 
623
-        ! Print help
774
+        ! Print help (two rows for better readability)
624
         print '(A)', ''
775
         print '(A)', ''
625
-        print '(A)', achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // &
776
+        print '(A)', 'Legend: ' // achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // &
626
                      achar(27) // '[31m✗' // achar(27) // '[0m=modified ' // &
777
                      achar(27) // '[31m✗' // achar(27) // '[0m=modified ' // &
627
-                     achar(27) // '[90m✗' // achar(27) // '[0m=untracked'
778
+                     achar(27) // '[90m✗' // achar(27) // '[0m=untracked ' // &
628
-        print '(A)', 'j/↓: down | k/↑: up | Space: stage file | q: quit'
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'
629
 
781
 
630
         call free_tree(root)
782
         call free_tree(root)
631
     end subroutine draw_interactive_tree
783
     end subroutine draw_interactive_tree
@@ -656,11 +808,13 @@ contains
656
         character(len=50) :: mark_unstaged
808
         character(len=50) :: mark_unstaged
657
         character(len=50) :: mark_untracked
809
         character(len=50) :: mark_untracked
658
         character(len=50) :: mark_staged
810
         character(len=50) :: mark_staged
811
+        character(len=50) :: mark_incoming
659
 
812
 
660
         ! Initialize colored marks with explicit ESC characters
813
         ! Initialize colored marks with explicit ESC characters
661
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
814
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
662
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
815
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
663
         write(mark_staged, '(A,A,A,A,A)') ESC, '[32m', ' ↑', ESC, '[0m'  ! Green for staged
816
         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
664
 
818
 
665
         ! Count children first
819
         ! Count children first
666
         n_children = 0
820
         n_children = 0
@@ -696,6 +850,9 @@ contains
696
                 if (node%is_untracked) then
850
                 if (node%is_untracked) then
697
                     line = trim(line) // trim(mark_untracked)
851
                     line = trim(line) // trim(mark_untracked)
698
                 end if
852
                 end if
853
+                if (node%has_incoming) then
854
+                    line = trim(line) // trim(mark_incoming)
855
+                end if
699
                 line = trim(line) // highlight_off
856
                 line = trim(line) // highlight_off
700
             else
857
             else
701
                 line = trim(line) // trim(node%name)
858
                 line = trim(line) // trim(node%name)
@@ -709,6 +866,9 @@ contains
709
                 if (node%is_untracked) then
866
                 if (node%is_untracked) then
710
                     line = trim(line) // trim(mark_untracked)
867
                     line = trim(line) // trim(mark_untracked)
711
                 end if
868
                 end if
869
+                if (node%has_incoming) then
870
+                    line = trim(line) // trim(mark_incoming)
871
+                end if
712
             end if
872
             end if
713
 
873
 
714
             print '(A)', trim(line)
874
             print '(A)', trim(line)
@@ -820,10 +980,10 @@ contains
820
         before = (trim(a%name) < trim(b%name))
980
         before = (trim(a%name) < trim(b%name))
821
     end function should_insert_before
981
     end function should_insert_before
822
 
982
 
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)
824
         type(tree_node), pointer, intent(in) :: node
984
         type(tree_node), pointer, intent(in) :: node
825
         character(len=*), intent(in) :: path
985
         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
827
 
987
 
828
         integer :: slash_pos, iostat
988
         integer :: slash_pos, iostat
829
         character(len=512) :: first_part, rest
989
         character(len=512) :: first_part, rest
@@ -843,6 +1003,7 @@ contains
843
                     child%is_staged = child%is_staged .or. is_staged
1003
                     child%is_staged = child%is_staged .or. is_staged
844
                     child%is_unstaged = child%is_unstaged .or. is_unstaged
1004
                     child%is_unstaged = child%is_unstaged .or. is_unstaged
845
                     child%is_untracked = child%is_untracked .or. is_untracked
1005
                     child%is_untracked = child%is_untracked .or. is_untracked
1006
+                    child%has_incoming = child%has_incoming .or. has_incoming
846
                     return
1007
                     return
847
                 end if
1008
                 end if
848
                 if (.not. associated(child%next_sibling)) exit
1009
                 if (.not. associated(child%next_sibling)) exit
@@ -869,6 +1030,7 @@ contains
869
             new_child%is_staged = is_staged
1030
             new_child%is_staged = is_staged
870
             new_child%is_unstaged = is_unstaged
1031
             new_child%is_unstaged = is_unstaged
871
             new_child%is_untracked = is_untracked
1032
             new_child%is_untracked = is_untracked
1033
+            new_child%has_incoming = has_incoming
872
             new_child%first_child => null()
1034
             new_child%first_child => null()
873
             new_child%next_sibling => null()
1035
             new_child%next_sibling => null()
874
 
1036
 
@@ -886,7 +1048,7 @@ contains
886
             child => node%first_child
1048
             child => node%first_child
887
             do while (associated(child))
1049
             do while (associated(child))
888
                 if (trim(child%name) == trim(first_part)) then
1050
                 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)
890
                     return
1052
                     return
891
                 end if
1053
                 end if
892
                 if (.not. associated(child%next_sibling)) exit
1054
                 if (.not. associated(child%next_sibling)) exit
@@ -900,6 +1062,7 @@ contains
900
             new_child%is_staged = .false.
1062
             new_child%is_staged = .false.
901
             new_child%is_unstaged = .false.
1063
             new_child%is_unstaged = .false.
902
             new_child%is_untracked = .false.
1064
             new_child%is_untracked = .false.
1065
+            new_child%has_incoming = .false.
903
             new_child%first_child => null()
1066
             new_child%first_child => null()
904
             new_child%next_sibling => null()
1067
             new_child%next_sibling => null()
905
 
1068
 
@@ -909,7 +1072,7 @@ contains
909
                 child%next_sibling => new_child
1072
                 child%next_sibling => new_child
910
             end if
1073
             end if
911
 
1074
 
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)
913
         end if
1076
         end if
914
     end subroutine add_to_tree
1077
     end subroutine add_to_tree
915
 
1078
 
@@ -933,11 +1096,13 @@ contains
933
         character(len=50) :: mark_unstaged
1096
         character(len=50) :: mark_unstaged
934
         character(len=50) :: mark_untracked
1097
         character(len=50) :: mark_untracked
935
         character(len=50) :: mark_staged
1098
         character(len=50) :: mark_staged
1099
+        character(len=50) :: mark_incoming
936
 
1100
 
937
         ! Initialize colored marks with explicit ESC characters
1101
         ! Initialize colored marks with explicit ESC characters
938
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
1102
         write(mark_unstaged, '(A,A,A,A,A)') ESC, '[31m', ' ✗', ESC, '[0m'  ! Red for modified
939
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
1103
         write(mark_untracked, '(A,A,A,A,A)') ESC, '[90m', ' ✗', ESC, '[0m'  ! Dim grey for untracked
940
         write(mark_staged, '(A,A,A,A,A)') ESC, '[32m', ' ↑', ESC, '[0m'  ! Green for staged
1104
         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
941
 
1106
 
942
         ! Count children first
1107
         ! Count children first
943
         n_children = 0
1108
         n_children = 0
@@ -965,6 +1130,9 @@ contains
965
             if (node%is_untracked) then
1130
             if (node%is_untracked) then
966
                 line = trim(line) // trim(mark_untracked)
1131
                 line = trim(line) // trim(mark_untracked)
967
             end if
1132
             end if
1133
+            if (node%has_incoming) then
1134
+                line = trim(line) // trim(mark_incoming)
1135
+            end if
968
             print '(A)', trim(line)
1136
             print '(A)', trim(line)
969
         end if
1137
         end if
970
 
1138