giterm 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -0
  3. data/giterm +245 -26
  4. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bb0366aa299a669ac0a598be355c7ac6a680c5779a0207b77ce08eb9f4ad201
4
- data.tar.gz: 5fbfaf7ec2512b2001deed99cb708a8ae5a00d4cb5fa00457f56cd193c1b53a0
3
+ metadata.gz: 2012953b93f04a86f90934c30f6329d026b3aceac176ce69f50aa00f962f45a7
4
+ data.tar.gz: 7f71c894db4123c5d7dc6d28ebb18717e207bc462c8ee6d9d4de53559b840b7a
5
5
  SHA512:
6
- metadata.gz: f3586f793514e2405031582c40017cbf9e934a553d63331c98ac475068ebcef3b1fb490b34c016aca8f5a05ebf6b54ae70f0c587e489bc69b91a347489782fcb
7
- data.tar.gz: bd9a1e651aa03c53e7289912c4f316c054b4d9e7953f953eb97f12f41ed75022470335e276d854d85109311b98fdefd255916be3bf139283f2f36696fb8897c3
6
+ metadata.gz: f73797cd5e08b42a23d50e5e70092b63505e6c412e679dd6cc3532de0887563a359a5693c96377cc0d01516c027ff8a40bd6b29041816c168b65a23a32a5ce04
7
+ data.tar.gz: 7a1881711325995593be38b5f339d534cc7003642b2e78dd8bafd8d9dd3c3384f078b1ddb83c97293961de52a3f5c679d8b73e339f48961d181219b6849d6129
data/README.md CHANGED
@@ -51,6 +51,28 @@ A powerful Git and GitHub Terminal User Interface (TUI) client written in Ruby u
51
51
  - `:` - Enter git command mode
52
52
  - `!` - Enter shell command mode
53
53
 
54
+ ## Screenshots
55
+
56
+ ### Git Repository Status View
57
+ Shows the enhanced status view with file changes and detailed diff information in the right pane:
58
+
59
+ ![Git Status View](screenshots/giterm_git_status.png)
60
+
61
+ ### Non-Git Directory Support
62
+ New in v1.1.0 - GiTerm now runs in non-git directories and offers GitHub integration:
63
+
64
+ ![Non-Git Directory](screenshots/giterm_non_git.png)
65
+
66
+ ### GitHub Repositories Mode
67
+ Browse all your GitHub repositories with detailed information and README preview:
68
+
69
+ ![GitHub Mode](screenshots/giterm_github_mode.png)
70
+
71
+ ### Help System
72
+ Comprehensive help showing all available keyboard shortcuts:
73
+
74
+ ![Help Screen](screenshots/giterm_help.png)
75
+
54
76
  ## Requirements
55
77
 
56
78
  - Ruby 2.7 or higher
@@ -136,6 +158,13 @@ GiTerm looks for configuration in the following order:
136
158
 
137
159
  ## What's New
138
160
 
161
+ ### Version 1.1.0
162
+ - 🏠 **Non-Git Directory Support** - GiTerm now runs outside git repositories!
163
+ - 📊 **Enhanced Local Repository Info** - Rich repository overview when working tree is clean
164
+ - 🔧 **Smart Mode Switching** - Seamless switching between local Git and GitHub modes
165
+ - 🎯 **Improved User Experience** - Better error handling and helpful guidance
166
+ - 📸 **Updated Documentation** - New screenshots showing all major features
167
+
139
168
  ### Version 1.0.0
140
169
  - 🎨 Organization color coding in GitHub mode
141
170
  - 📜 Right pane scrolling with Shift+Arrow keys
data/giterm CHANGED
@@ -7,7 +7,7 @@
7
7
  # Author: Geir Isene <g@isene.com> (adapted from RTFM)
8
8
  # Github: https://github.com/isene/GiTerm
9
9
  # License: Public domain
10
- @version = '1.0.0'
10
+ @version = '1.1.0'
11
11
 
12
12
  # SAVE & STORE TERMINAL {{{1
13
13
  ORIG_STTY = `stty -g 2>/dev/null`.chomp rescue ''
@@ -421,6 +421,11 @@ def update_bottom_legends
421
421
  end
422
422
 
423
423
  def update_repo_info
424
+ unless @is_git_repo
425
+ @p_top.say('[No Git Repo]'.fg(196) + ' | ' + 'GitHub Mode Available'.fg(154))
426
+ return
427
+ end
428
+
424
429
  branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
425
430
  remote = `git config --get remote.origin.url 2>/dev/null`.strip
426
431
 
@@ -436,7 +441,7 @@ end
436
441
 
437
442
  def update_mode_info(mode_title)
438
443
  @current_mode_title = mode_title
439
- if @current_branch
444
+ if @current_branch && @current_repo
440
445
  info = "[#{mode_title}]".fg(156) + " | " + @current_repo.fg(249) + " | " + @current_branch.fg(154)
441
446
  else
442
447
  info = "[#{mode_title}]".fg(156)
@@ -447,6 +452,23 @@ end
447
452
  # GIT FUNCTIONS {{{1
448
453
  def git_status
449
454
  @mode = :status
455
+
456
+ unless @is_git_repo
457
+ @status_items = []
458
+ @p_left.clear
459
+ @p_left.say('Not in a Git repository'.fg(196) + "\n\n" +
460
+ 'Press TAB to switch to GitHub mode'.fg(245))
461
+ @p_right.clear
462
+ @p_right.say('To use local Git features:'.fg(156) + "\n\n" +
463
+ '1. Navigate to a Git repository'.fg(245) + "\n" +
464
+ '2. Run giterm from within the repository'.fg(245) + "\n\n" +
465
+ 'Or press TAB to use GitHub features'.fg(154))
466
+ @max_index = 0
467
+ @min_index = 0
468
+ @index = 0
469
+ return
470
+ end
471
+
450
472
  @status_items = []
451
473
  @p_left.ix = 0 # Reset scroll position
452
474
 
@@ -468,6 +490,7 @@ def git_status
468
490
  @index = [@index, @min_index].max
469
491
 
470
492
  display_status
493
+ update_right_pane # Show repo overview when working tree is clean
471
494
  end
472
495
 
473
496
  def display_status
@@ -519,11 +542,101 @@ def display_status
519
542
  @p_left.say(content)
520
543
  @p_left.ix = saved_ix
521
544
 
522
- # Show diff in right pane if file selected
523
- show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
545
+ # Show diff in right pane if file selected, or repo overview if clean
546
+ if @status_items.empty?
547
+ show_repo_overview
548
+ else
549
+ show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
550
+ end
551
+ end
552
+
553
+ def show_repo_overview
554
+ unless @is_git_repo
555
+ return
556
+ end
557
+
558
+ content = "Repository Overview\n".b.fg(156)
559
+ content += ('=' * 30) + "\n\n"
560
+
561
+ # Get repository information
562
+ branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
563
+ remote = `git config --get remote.origin.url 2>/dev/null`.strip
564
+ commit_count = `git rev-list --count HEAD 2>/dev/null`.strip
565
+ last_commit = `git log -1 --pretty=format:'%h %s (%cr by %an)' 2>/dev/null`.strip
566
+ contributors = `git shortlog -sn 2>/dev/null | head -5`.strip
567
+ branches_count = `git branch -r 2>/dev/null | wc -l`.strip
568
+ tags_count = `git tag 2>/dev/null | wc -l`.strip
569
+ stash_count = `git stash list 2>/dev/null | wc -l`.strip
570
+
571
+ # Repository name and remote
572
+ if !remote.empty?
573
+ repo_name = File.basename(remote, '.git')
574
+ content += "Repository: ".fg(249) + repo_name.fg(220) + "\n"
575
+ content += "Remote: ".fg(249) + remote.fg(245) + "\n"
576
+ else
577
+ content += "Repository: ".fg(249) + "Local only (no remote)".fg(245) + "\n"
578
+ end
579
+
580
+ content += "\n"
581
+
582
+ # Current branch
583
+ content += "Current branch: ".fg(249) + branch.fg(154) + "\n"
584
+
585
+ # Statistics
586
+ content += "Total commits: ".fg(249) + commit_count.fg(51) + "\n"
587
+ content += "Branches: ".fg(249) + branches_count.fg(51) + "\n"
588
+ content += "Tags: ".fg(249) + tags_count.fg(51) + "\n"
589
+
590
+ if stash_count.to_i > 0
591
+ content += "Stashed changes: ".fg(249) + stash_count.fg(220) + "\n"
592
+ end
593
+
594
+ content += "\n"
595
+
596
+ # Last commit
597
+ if !last_commit.empty?
598
+ content += "Last commit:\n".fg(156)
599
+ content += last_commit.fg(245) + "\n\n"
600
+ end
601
+
602
+ # Top contributors
603
+ if !contributors.empty?
604
+ content += "Top contributors:\n".fg(156)
605
+ contributors.lines.each do |line|
606
+ count, name = line.strip.split("\t", 2)
607
+ content += " #{count.rjust(4).fg(51)} #{name.fg(249)}\n" if name
608
+ end
609
+ content += "\n"
610
+ end
611
+
612
+ # Check for uncommitted changes
613
+ status = `git status --porcelain 2>/dev/null`
614
+ if status.empty?
615
+ content += "✓ Working tree clean".fg(154) + "\n"
616
+ else
617
+ modified = status.lines.count { |l| l[0..1].include?('M') }
618
+ added = status.lines.count { |l| l[0..1].include?('A') }
619
+ deleted = status.lines.count { |l| l[0..1].include?('D') }
620
+ untracked = status.lines.count { |l| l[0..1] == '??' }
621
+
622
+ content += "Uncommitted changes:\n".fg(220)
623
+ content += " Modified: #{modified}\n".fg(214) if modified > 0
624
+ content += " Added: #{added}\n".fg(154) if added > 0
625
+ content += " Deleted: #{deleted}\n".fg(196) if deleted > 0
626
+ content += " Untracked: #{untracked}\n".fg(245) if untracked > 0
627
+ end
628
+
629
+ @p_right.clear
630
+ @p_right.say(content)
524
631
  end
525
632
 
526
633
  def show_file_diff(file)
634
+ unless @is_git_repo
635
+ @p_right.clear
636
+ @p_right.say('Not in a Git repository'.fg(196))
637
+ return
638
+ end
639
+
527
640
  diff = `git diff #{Shellwords.escape(file)} 2>/dev/null`
528
641
  diff = `git diff --cached #{Shellwords.escape(file)} 2>/dev/null` if diff.empty?
529
642
 
@@ -549,6 +662,14 @@ def show_file_diff(file)
549
662
  end
550
663
 
551
664
  def git_log
665
+ unless @is_git_repo
666
+ @p_left.clear
667
+ @p_left.say('Not in a Git repository'.fg(196))
668
+ @p_right.clear
669
+ @p_right.say('Git log unavailable outside repositories'.fg(245))
670
+ return
671
+ end
672
+
552
673
  @mode = :log
553
674
  @log_entries = []
554
675
  @p_left.ix = 0 # Reset scroll position
@@ -594,6 +715,14 @@ def show_commit_details(hash)
594
715
  end
595
716
 
596
717
  def git_branches
718
+ unless @is_git_repo
719
+ @p_left.clear
720
+ @p_left.say('Not in a Git repository'.fg(196))
721
+ @p_right.clear
722
+ @p_right.say('Git branches unavailable outside repositories'.fg(245))
723
+ return
724
+ end
725
+
597
726
  @mode = :branches
598
727
  @branches = []
599
728
  @p_left.ix = 0 # Reset scroll position
@@ -635,6 +764,11 @@ def display_branches
635
764
  end
636
765
 
637
766
  def git_pull
767
+ unless @is_git_repo
768
+ @p_bottom.say('Not in a Git repository'.fg(196))
769
+ return
770
+ end
771
+
638
772
  @p_bottom.say('Pulling from remote...')
639
773
  result = `git pull 2>&1`
640
774
  @p_bottom.say(result.lines.first.strip)
@@ -642,6 +776,11 @@ def git_pull
642
776
  end
643
777
 
644
778
  def git_push
779
+ unless @is_git_repo
780
+ @p_bottom.say('Not in a Git repository'.fg(196))
781
+ return
782
+ end
783
+
645
784
  @p_bottom.say('Pushing to remote...')
646
785
  result = `git push 2>&1`
647
786
  @p_bottom.say(result.lines.first.strip)
@@ -772,6 +911,66 @@ def github_request_curl(endpoint)
772
911
  end
773
912
  end
774
913
 
914
+ def show_github_setup_in_nongit
915
+ @mode = :status # Use status mode but show special message
916
+ @status_items = [] # Empty items list
917
+
918
+ content = "GiTerm - GitHub Terminal UI\n".b.fg(156)
919
+ content += ('=' * 35) + "\n\n"
920
+
921
+ content += "Not in a Git repository\n".fg(196)
922
+ content += "Local Git features unavailable\n\n".fg(245)
923
+
924
+ if @github_token.empty?
925
+ content += "[!] No GitHub token found\n\n".fg(196)
926
+ content += "To access GitHub features:\n".fg(249)
927
+ content += "Press ".fg(249) + "'T'".fg(51) + " for guided token setup\n\n".fg(249)
928
+ content += "Or set GITHUB_TOKEN environment variable\n".fg(245)
929
+ else
930
+ content += "GitHub token configured!\n".fg(154)
931
+ content += "Press TAB to view GitHub repositories\n".fg(249)
932
+ end
933
+
934
+ content += "\n\nKeyboard shortcuts:\n".fg(156)
935
+ content += "TAB".fg(220) + " - Switch to GitHub mode\n".fg(245)
936
+ content += "T".fg(220) + " - Set up GitHub token\n".fg(245)
937
+ content += "q".fg(220) + " - Quit GiTerm\n".fg(245)
938
+ content += "?".fg(220) + " - Show help\n".fg(245)
939
+
940
+ @p_left.clear
941
+ @p_left.say(content)
942
+
943
+ @p_right.clear
944
+ if @github_token.empty?
945
+ help_text = "GitHub Setup Guide\n".b.fg(156)
946
+ help_text += ('=' * 25) + "\n\n"
947
+ help_text += "1. Press 'T' to start token setup\n".fg(249)
948
+ help_text += "2. Follow the instructions\n".fg(249)
949
+ help_text += "3. Create a Personal Access Token\n".fg(249)
950
+ help_text += " with 'repo' permissions\n".fg(249)
951
+ help_text += "4. Paste the token when prompted\n\n".fg(249)
952
+ help_text += "Benefits of GitHub integration:\n".fg(154)
953
+ help_text += "• Browse all your repositories\n".fg(245)
954
+ help_text += "• View issues and pull requests\n".fg(245)
955
+ help_text += "• Search GitHub repositories\n".fg(245)
956
+ help_text += "• Access private repos\n".fg(245)
957
+ @p_right.say(help_text)
958
+ else
959
+ @p_right.say('Press TAB to switch to GitHub mode'.fg(154))
960
+ end
961
+
962
+ # Set up proper indices for main loop
963
+ @max_index = 0
964
+ @min_index = 0
965
+ @index = 0
966
+
967
+ # Update mode info
968
+ update_mode_info('Not in Git Repository')
969
+
970
+ # Show bottom legends
971
+ update_bottom_legends
972
+ end
973
+
775
974
  def github_repos
776
975
  @mode = :github_repos
777
976
  @github_repos = []
@@ -1275,6 +1474,7 @@ def github_search_repositories
1275
1474
  @mode = :github_search
1276
1475
  @github_search_results = []
1277
1476
  @index = 0
1477
+ @p_left.ix = 0 # Reset scroll position
1278
1478
 
1279
1479
  # Perform the search
1280
1480
  result = github_request("/search/repositories?q=#{CGI.escape(query)}&sort=stars&order=desc&per_page=50")
@@ -2049,7 +2249,13 @@ def update_right_pane(fast_update = false)
2049
2249
 
2050
2250
  case @mode
2051
2251
  when :status
2052
- show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
2252
+ if @is_git_repo
2253
+ if @status_items.empty?
2254
+ show_repo_overview
2255
+ else
2256
+ show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
2257
+ end
2258
+ end
2053
2259
  when :log
2054
2260
  show_commit_details(@log_entries[@index]) if @index >= 0 && @index < @log_entries.length
2055
2261
  when :branches
@@ -2125,7 +2331,7 @@ def show_help
2125
2331
  end
2126
2332
 
2127
2333
  def stage_file
2128
- return unless @mode == :status && @index < @status_items.length
2334
+ return unless @is_git_repo && @mode == :status && @index < @status_items.length
2129
2335
 
2130
2336
  file = @status_items[@index][:file]
2131
2337
  `git add #{Shellwords.escape(file)}`
@@ -2133,7 +2339,7 @@ def stage_file
2133
2339
  end
2134
2340
 
2135
2341
  def unstage_file
2136
- return unless @mode == :status && @index < @status_items.length
2342
+ return unless @is_git_repo && @mode == :status && @index < @status_items.length
2137
2343
 
2138
2344
  file = @status_items[@index][:file]
2139
2345
  `git reset HEAD #{Shellwords.escape(file)}`
@@ -2141,6 +2347,11 @@ def unstage_file
2141
2347
  end
2142
2348
 
2143
2349
  def commit_changes
2350
+ unless @is_git_repo
2351
+ @p_bottom.say('Not in a Git repository'.fg(196))
2352
+ return
2353
+ end
2354
+
2144
2355
  @p_bottom.clear
2145
2356
  @p_bottom.say('Commit message: ')
2146
2357
 
@@ -2358,17 +2569,19 @@ def main_loop
2358
2569
  log_debug('Entered main loop')
2359
2570
  loop_count = 0
2360
2571
  loop do
2361
-
2362
- loop_count += 1
2363
- log_debug("Main loop iteration #{loop_count}") if loop_count % 100 == 1
2364
- render
2365
- chr = getkey
2366
- handle_key(chr)
2367
- rescue => e
2368
- log_debug("Error in main loop: #{e.class}: #{e.message}")
2369
- @p_bottom.say("Error: #{e.message}") if @p_bottom
2370
- sleep 1
2371
-
2572
+ begin
2573
+ loop_count += 1
2574
+ log_debug("Main loop iteration #{loop_count}") if loop_count % 100 == 1
2575
+ render
2576
+ chr = getkey
2577
+ handle_key(chr)
2578
+ rescue => e
2579
+ log_debug("Error in main loop: #{e.class}: #{e.message}")
2580
+ log_debug("Backtrace: #{e.backtrace.first(5).join(' | ')}")
2581
+ @p_bottom.say("Error: #{e.message}") if @p_bottom
2582
+ sleep 1
2583
+ # Continue the loop after error
2584
+ end
2372
2585
  end
2373
2586
  rescue Interrupt
2374
2587
  log_debug('Received interrupt')
@@ -2388,12 +2601,12 @@ begin
2388
2601
 
2389
2602
  log_debug('Checking Git repository...')
2390
2603
  # Check if we're in a Git repository
2391
- unless system('git rev-parse --git-dir >/dev/null 2>&1')
2392
- log_debug('Not in Git repo, exiting')
2393
- puts 'GiTerm must be run from within a Git repository'
2394
- exit 1
2604
+ @is_git_repo = system('git rev-parse --git-dir >/dev/null 2>&1')
2605
+ if @is_git_repo
2606
+ log_debug('Git repo check passed')
2607
+ else
2608
+ log_debug('Not in Git repo, but continuing...')
2395
2609
  end
2396
- log_debug('Git repo check passed')
2397
2610
 
2398
2611
  log_debug('Loading configuration...')
2399
2612
  load_config
@@ -2403,9 +2616,15 @@ begin
2403
2616
  init_windows
2404
2617
  log_debug('Windows initialized')
2405
2618
 
2406
- log_debug('Getting git status...')
2407
- git_status # Start with git status view
2408
- log_debug('Git status retrieved')
2619
+ if @is_git_repo
2620
+ log_debug('Getting git status...')
2621
+ git_status # Start with git status view
2622
+ log_debug('Git status retrieved')
2623
+ else
2624
+ # Start in non-git mode
2625
+ log_debug('Not in git repo, showing non-git UI...')
2626
+ show_github_setup_in_nongit
2627
+ end
2409
2628
 
2410
2629
  update_bottom_legends
2411
2630
  log_debug('Starting main loop...')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: giterm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: "."
10
10
  cert_chain: []
11
- date: 2025-08-05 00:00:00.000000000 Z
11
+ date: 2025-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -54,7 +54,8 @@ dependencies:
54
54
  version: '13.0'
55
55
  description: GiTerm is a powerful terminal interface for Git and GitHub, providing
56
56
  an intuitive TUI for repository management, issue tracking, and pull request handling.
57
- Features include color-coded organizations, smart fetching, and vim-like navigation.
57
+ Now works in non-git directories! Features include GitHub integration, enhanced
58
+ repository overview, smart fetching, and vim-like navigation.
58
59
  email:
59
60
  - g@isene.com
60
61
  executables: