giterm 1.0.0 → 1.1.0

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 +244 -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: 1031719142e9a102c9785ad09e67227777b8fb60ce09ab89e2cf8c8111536e5a
4
+ data.tar.gz: 8ed0347f1eca624142d53b6516928d3eb95e67eee2ecc31746d5530ca992ba81
5
5
  SHA512:
6
- metadata.gz: f3586f793514e2405031582c40017cbf9e934a553d63331c98ac475068ebcef3b1fb490b34c016aca8f5a05ebf6b54ae70f0c587e489bc69b91a347489782fcb
7
- data.tar.gz: bd9a1e651aa03c53e7289912c4f316c054b4d9e7953f953eb97f12f41ed75022470335e276d854d85109311b98fdefd255916be3bf139283f2f36696fb8897c3
6
+ metadata.gz: ce6ecfe8c35cc92d3d5bf5ecb3c44ce880a22c34da13bbed42f0aecaa5ed4072a227e4dcd8dccfb6a571b248c56dc04a520158485f588244701524e5101c0913
7
+ data.tar.gz: c454493a5c3ae04a5745e6905ae81e9fbbdaab731354f413796842a41ceea83fd389400bb8086b4af033acb2dff07e024c3719e9084c92305911fd7933b0fdfb
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 = []
@@ -2049,7 +2248,13 @@ def update_right_pane(fast_update = false)
2049
2248
 
2050
2249
  case @mode
2051
2250
  when :status
2052
- show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
2251
+ if @is_git_repo
2252
+ if @status_items.empty?
2253
+ show_repo_overview
2254
+ else
2255
+ show_file_diff(@status_items[@index][:file]) if @index >= 0 && @index < @status_items.length
2256
+ end
2257
+ end
2053
2258
  when :log
2054
2259
  show_commit_details(@log_entries[@index]) if @index >= 0 && @index < @log_entries.length
2055
2260
  when :branches
@@ -2125,7 +2330,7 @@ def show_help
2125
2330
  end
2126
2331
 
2127
2332
  def stage_file
2128
- return unless @mode == :status && @index < @status_items.length
2333
+ return unless @is_git_repo && @mode == :status && @index < @status_items.length
2129
2334
 
2130
2335
  file = @status_items[@index][:file]
2131
2336
  `git add #{Shellwords.escape(file)}`
@@ -2133,7 +2338,7 @@ def stage_file
2133
2338
  end
2134
2339
 
2135
2340
  def unstage_file
2136
- return unless @mode == :status && @index < @status_items.length
2341
+ return unless @is_git_repo && @mode == :status && @index < @status_items.length
2137
2342
 
2138
2343
  file = @status_items[@index][:file]
2139
2344
  `git reset HEAD #{Shellwords.escape(file)}`
@@ -2141,6 +2346,11 @@ def unstage_file
2141
2346
  end
2142
2347
 
2143
2348
  def commit_changes
2349
+ unless @is_git_repo
2350
+ @p_bottom.say('Not in a Git repository'.fg(196))
2351
+ return
2352
+ end
2353
+
2144
2354
  @p_bottom.clear
2145
2355
  @p_bottom.say('Commit message: ')
2146
2356
 
@@ -2358,17 +2568,19 @@ def main_loop
2358
2568
  log_debug('Entered main loop')
2359
2569
  loop_count = 0
2360
2570
  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
-
2571
+ begin
2572
+ loop_count += 1
2573
+ log_debug("Main loop iteration #{loop_count}") if loop_count % 100 == 1
2574
+ render
2575
+ chr = getkey
2576
+ handle_key(chr)
2577
+ rescue => e
2578
+ log_debug("Error in main loop: #{e.class}: #{e.message}")
2579
+ log_debug("Backtrace: #{e.backtrace.first(5).join(' | ')}")
2580
+ @p_bottom.say("Error: #{e.message}") if @p_bottom
2581
+ sleep 1
2582
+ # Continue the loop after error
2583
+ end
2372
2584
  end
2373
2585
  rescue Interrupt
2374
2586
  log_debug('Received interrupt')
@@ -2388,12 +2600,12 @@ begin
2388
2600
 
2389
2601
  log_debug('Checking Git repository...')
2390
2602
  # 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
2603
+ @is_git_repo = system('git rev-parse --git-dir >/dev/null 2>&1')
2604
+ if @is_git_repo
2605
+ log_debug('Git repo check passed')
2606
+ else
2607
+ log_debug('Not in Git repo, but continuing...')
2395
2608
  end
2396
- log_debug('Git repo check passed')
2397
2609
 
2398
2610
  log_debug('Loading configuration...')
2399
2611
  load_config
@@ -2403,9 +2615,15 @@ begin
2403
2615
  init_windows
2404
2616
  log_debug('Windows initialized')
2405
2617
 
2406
- log_debug('Getting git status...')
2407
- git_status # Start with git status view
2408
- log_debug('Git status retrieved')
2618
+ if @is_git_repo
2619
+ log_debug('Getting git status...')
2620
+ git_status # Start with git status view
2621
+ log_debug('Git status retrieved')
2622
+ else
2623
+ # Start in non-git mode
2624
+ log_debug('Not in git repo, showing non-git UI...')
2625
+ show_github_setup_in_nongit
2626
+ end
2409
2627
 
2410
2628
  update_bottom_legends
2411
2629
  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.0
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-06 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: