gitflash 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37431db670588b8025d973c045532de047b33a667fc1c86152e1180d2d0a01a6
4
- data.tar.gz: 307fe7991415b2bedce69a08f7b0e09e591be3cbcb6be692c1d46c87c79019b1
3
+ metadata.gz: 308e1c6f4eba1ab9251d7606e919e91108df058d0618b04e2c8f35aac8370ad0
4
+ data.tar.gz: 47da55dc51b8f5b38e6dae281560f0dc880c3a58a95baea0689d4b0fd82536f7
5
5
  SHA512:
6
- metadata.gz: dcc8b7a6a6c740c46c78d29766bc78b99fa9949217482a71e47070a077a51759903c74a82adbcbe24590e27dbc04f5db5501175efda1a2a624f7b84211e72579
7
- data.tar.gz: 541587b9f335e4ccd47f4cb0e7ec9806d1e4a2abb06d8811e81e8c483c2eba2126c26f7cbaebe5074d07d968d4e156b6ea751ccfbff08b6128a6239af0cd4272
6
+ metadata.gz: e21748dad35cd17a28452b9593dc08b015544d08d68fb615c4ae71e7681a3ac7790a2a95f0eec2aaad389d46fe95fb63d969d8043b668f695e39e7903afef640
7
+ data.tar.gz: e91e1e12645b2af6d0079209c690e08b4dfdb0e951cf99cf5b1c01b2b831e79d890a3eec1450be09434d14d448cae9cc58d93ccf61aded66e246da9c711bc670
data/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0.alpha] - 2023-06-17
4
+
5
+ - Initial release
6
+
7
+ ## [0.1.1.alpha] - 2023-10-28
8
+
9
+ - Fix delete method
10
+ - Added and updated test
11
+ - bug fixes
12
+
13
+ ## [0.1.2.alpha] - 2023-11-4
14
+
15
+ - Refactors and code improvements
16
+ - Zeitwerk warning suppress
17
+ - Update README
18
+ - Bug fixes
19
+
20
+ ## [0.2.0.alpha] - 2023-12-9
21
+
22
+ - Add reset command
23
+ - Code improvements
24
+
25
+ ## [0.3.0] - 2025-10-10
26
+
27
+ - Fix reset method
28
+ - Update to Ruby version 3.3.2
29
+ - Bug fixes
30
+
31
+ ## [0.4.0] - 2026-09-24
32
+
33
+ - Run git commands without a shell and raise on failure
34
+ - List branches with `git for-each-ref`; detached HEAD no longer shows as a branch
35
+ - List the latest 100 commits for `reset`, unaffected by `log.decorate`
36
+ - Exit with an error when a git command fails or outside a git repository
37
+ - `delete` does nothing when no branches are available or selected
38
+ - `reset --hard` prints `Exited` when declined; fix single-commit message
39
+ - Correct `reset` help text
40
+ - Portable `bin/gitflash` shebang
41
+ - Require thor >= 1.4 (CVE-2025-54314) and Ruby >= 3.3
42
+ - Add `version` command (`--version`, `-v`)
43
+ - CI: pin third-party action to a commit SHA, restrict token permissions, test Ruby 3.3–3.4
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/gitflash.svg)](https://badge.fury.io/rb/gitflash)
4
4
 
5
- ⚠️ **This project is stil under development**: Use at your own risk!
5
+ ⚠️ **This project is still under development**: Use at your own risk!
6
6
 
7
7
  This gem allows you to use some of the most common git commands in a more user intuitive way.
8
8
 
@@ -24,10 +24,21 @@ Or install it yourself as:
24
24
 
25
25
  ## Requirements
26
26
 
27
- This gem requires Ruby 3.3.2+.
27
+ This gem requires Ruby 3.3+ and git.
28
+
28
29
  ## Usage
29
30
 
30
- Run `gitflash` to get a list with the available commands and their description.
31
+ Run `gitflash` inside a git repository to get a list with the available commands and their description.
32
+
33
+ | Command | Description |
34
+ | --- | --- |
35
+ | `gitflash checkout` | Select a local branch and check it out |
36
+ | `gitflash delete` | Select local branches (excluding the current branch and main/master) and force-delete them |
37
+ | `gitflash reset` | Select one of the latest 100 commits and reset to it (mixed reset) |
38
+ | `gitflash reset --hard` | Same as `reset`, but discard all current changes after confirmation |
39
+ | `gitflash version` | Print the installed version (also `--version`, `-v`) |
40
+
41
+ Run `gitflash help <command>` for details on a command.
31
42
 
32
43
  ## Development
33
44
 
data/bin/gitflash CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby -wU
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'gitflash'
4
5
  require 'gitflash/cli'
@@ -22,12 +22,12 @@ delete:
22
22
  reset:
23
23
  short: 'Reset your code to the selected commit'
24
24
  long: |
25
- Accepts an optional parameter (hard) if you want to hard reset.
25
+ By default performs a mixed reset: your changes are kept as unstaged changes.
26
+ Pass --hard to discard all your current changes.
26
27
 
27
- Presents a list with all the available commits.
28
+ Presents a list with the latest 100 commits of the current branch.
28
29
 
29
30
  You can use the arrow keys to navigate through the commits.
30
31
  Type for quick searching a specific commit.
31
- Press `space` to choose multiple commits.
32
32
  Press `enter` to confirm selection and reset to that commit.
33
33
  Press `ctrl+c` to cancel the operation.
data/lib/gitflash/cli.rb CHANGED
@@ -14,6 +14,7 @@ module Gitflash
14
14
  long_desc descriptions.checkout.long
15
15
 
16
16
  def checkout
17
+ ensure_git_repo!
17
18
  branches? ? checkout_branch : prompt.ok('You only have one branch!')
18
19
  end
19
20
 
@@ -21,6 +22,7 @@ module Gitflash
21
22
  long_desc descriptions.delete.long
22
23
 
23
24
  def delete
25
+ ensure_git_repo!
24
26
  branches? ? delete_branch : prompt.ok('You only have one branch!')
25
27
  end
26
28
 
@@ -29,7 +31,15 @@ module Gitflash
29
31
 
30
32
  option :hard, type: :boolean, default: false, desc: 'Perform a hard reset'
31
33
  def reset
32
- commits? ? reset_to_commit : prompt.ok('You only have one branch!')
34
+ ensure_git_repo!
35
+ commits? ? reset_to_commit : prompt.ok('You only have one commit!')
36
+ end
37
+
38
+ desc 'version', 'Print the gitflash version'
39
+ map %w[--version -v] => :version
40
+
41
+ def version
42
+ puts VERSION
33
43
  end
34
44
 
35
45
  private
@@ -41,14 +51,15 @@ module Gitflash
41
51
  **checkout_options
42
52
  )
43
53
 
44
- git.checkout(selection)
54
+ run_git('checkout') { git.checkout(selection) }
45
55
  end
46
56
 
47
57
  def delete_branch
48
- selection = prompt.multi_select(
49
- 'Select branches to delete',
50
- branches(options: { master: false, current: false })
51
- )
58
+ choices = branches(options: { master: false, current: false })
59
+ return prompt.ok('No branches available to delete') if choices.empty?
60
+
61
+ selection = prompt.multi_select('Select branches to delete', choices)
62
+ return prompt.ok('No branches selected') if selection.empty?
52
63
 
53
64
  warning_message = <<~TEXT
54
65
  You are about to permanently delete the following branches even if they have unmerged changes:
@@ -56,7 +67,9 @@ module Gitflash
56
67
  #{selection.map { |br| "* #{br}" }.join("\n")}
57
68
  TEXT
58
69
 
59
- prompt_proceed_warning(warning_message) { git.delete(selection) }
70
+ proceed_with_warning(warning_message) do
71
+ run_git('delete') { git.delete(selection) }
72
+ end
60
73
  end
61
74
 
62
75
  def reset_to_commit
@@ -65,12 +78,27 @@ module Gitflash
65
78
 
66
79
  if options[:hard]
67
80
  warning_message = 'You are about to reset your branch and lose all your current changes'
68
- prompt.proceed_with_warning(warning_message) { git.reset(**reset_options) }
81
+ proceed_with_warning(warning_message) { run_git('reset') { git.reset(**reset_options) } }
69
82
  else
70
- git.reset(**reset_options)
83
+ run_git('reset') { git.reset(**reset_options) }
71
84
  end
72
85
  end
73
86
 
87
+ def proceed_with_warning(message, &)
88
+ result = prompt.proceed_with_warning(message, &)
89
+ puts result if result == 'Exited'
90
+ end
91
+
92
+ def run_git(command)
93
+ raise Thor::Error, "git #{command} failed" unless yield
94
+ end
95
+
96
+ def ensure_git_repo!
97
+ git.inside_work_tree!
98
+ rescue Git::CommandError
99
+ raise Thor::Error, 'Not a git repository'
100
+ end
101
+
74
102
  def branches?
75
103
  all_branches && all_branches.size > 1
76
104
  end
@@ -83,15 +111,9 @@ module Gitflash
83
111
  @prompt ||= Prompt.create
84
112
  end
85
113
 
86
- def prompt_proceed_warning(message)
87
- prompt.warn(message)
88
- answer = prompt.yes?('Do you want to proceed?')
89
- puts answer ? yield : 'Exited'
90
- end
91
-
92
114
  def checkout_options
93
115
  {}.tap do |opt|
94
- opt[:default] = current unless current.nil?
116
+ opt[:default] = current unless current.to_s.empty?
95
117
  end
96
118
  end
97
119
 
@@ -108,7 +130,7 @@ module Gitflash
108
130
  end
109
131
 
110
132
  def branch_commits
111
- @branch_commits = git.branch_commits
133
+ @branch_commits ||= git.branch_commits
112
134
  end
113
135
 
114
136
  def git
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Gitflash
@@ -20,7 +22,7 @@ module Gitflash
20
22
  keys = data.keys.map(&:to_sym)
21
23
  Struct.new(*keys).new(*data.values).tap do |st|
22
24
  keys.each do |method|
23
- st.send("#{method}=".to_sym, transform_to_struct(st.send(method)))
25
+ st.public_send("#{method}=", transform_to_struct(st.public_send(method)))
24
26
  end
25
27
  end
26
28
  end
@@ -1,15 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+
3
5
  module Gitflash
4
6
  module Git
5
7
  class BashCommand
6
8
  class << self
7
- def exec(command)
8
- `#{command}`
9
+ # Runs a command without a shell and returns its stdout.
10
+ # Raises CommandError when the command exits with a non-zero status.
11
+ def exec(*args)
12
+ stdout, stderr, status = Open3.capture3(*args)
13
+ raise CommandError, "#{args.join(' ')} failed: #{stderr.strip}" unless status.success?
14
+
15
+ stdout
16
+ rescue SystemCallError => e
17
+ raise CommandError, "#{args.join(' ')} failed: #{e.message}"
9
18
  end
10
19
 
11
- def system_exec(*args)
12
- system(*args)
20
+ def system_exec(*)
21
+ system(*)
13
22
  end
14
23
  end
15
24
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitflash
4
+ module Git
5
+ class CommandError < StandardError; end
6
+ end
7
+ end
@@ -3,25 +3,29 @@
3
3
  module Gitflash
4
4
  module Git
5
5
  class Wrapper
6
- HIDDEN_BRANCHES = %w[–show-current].freeze
6
+ COMMITS_LIMIT = 100
7
7
 
8
8
  class << self
9
9
  def local_branches(current: true, master: true)
10
- formatted_branches.tap do |branches|
11
- hidden_branches(current, master).each { |br| branches.delete(br) }
12
- end
10
+ formatted_branches - hidden_branches(current, master)
13
11
  end
14
12
 
15
13
  def all_local_branches
16
14
  formatted_branches
17
15
  end
18
16
 
17
+ # Raises CommandError when the working directory is not inside a git repository
18
+ def inside_work_tree!
19
+ output = bash.exec('git', 'rev-parse', '--is-inside-work-tree').strip
20
+ raise CommandError, 'Not inside a git work tree' unless output == 'true'
21
+ end
22
+
19
23
  def current_branch
20
- bash.exec('git branch --show-current').strip
24
+ bash.exec('git', 'branch', '--show-current').strip
21
25
  end
22
26
 
23
27
  def checkout(branch)
24
- bash.system_exec('git', 'checkout', branch)
28
+ bash.system_exec('git', 'checkout', branch, '--')
25
29
  end
26
30
 
27
31
  def delete(branch)
@@ -33,42 +37,44 @@ module Gitflash
33
37
  ar << '--hard' if hard
34
38
  end
35
39
 
36
- bash.system_exec('git', 'reset', *params, commit_hash)
40
+ bash.system_exec('git', 'reset', *params, commit_hash, '--')
37
41
  end
38
42
 
39
43
  def branch_commits
40
- commits_string = bash.exec('git log --oneline')
44
+ commits_string = bash.exec(
45
+ 'git', 'log', "--max-count=#{COMMITS_LIMIT}", '--format=%h%x09%s'
46
+ )
47
+ parse_commits(commits_string)
48
+ rescue CommandError
49
+ # `git log` fails on a branch without commits
50
+ {}
51
+ end
52
+
53
+ private
54
+
55
+ def parse_commits(commits_string)
41
56
  {}.tap do |hsh|
42
57
  commits_string.each_line do |line|
43
- parts = line.strip.split
44
- commit_code = parts[0]
45
- commit_name = parts[1..].join(' ')
46
- label = "#{commit_code} - #{commit_name}"
47
- hsh[label] = commit_code
58
+ commit_code, commit_name = line.chomp.split("\t", 2)
59
+ next if commit_code.nil? || commit_code.empty?
60
+
61
+ hsh["#{commit_code} - #{commit_name}"] = commit_code
48
62
  end
49
63
  end
50
64
  end
51
65
 
52
- private
53
-
54
66
  def hidden_branches(current, master)
55
67
  [].tap { |hb|
56
- hb.push('master') unless master
57
- hb.push('main') unless master
68
+ hb.push('master', 'main') unless master
58
69
  hb.push(current_branch) unless current
59
- } + HIDDEN_BRANCHES
70
+ }.reject(&:empty?)
60
71
  end
61
72
 
62
73
  def formatted_branches
63
- raw_branches.map do |str|
64
- next str unless str.start_with?('* ')
65
-
66
- str.gsub('* ', '')
67
- end
68
- end
69
-
70
- def raw_branches
71
- bash.exec('git branch').strip.split("\n").map(&:strip)
74
+ bash.exec('git', 'for-each-ref', '--format=%(refname:short)', 'refs/heads/')
75
+ .split("\n")
76
+ .map(&:strip)
77
+ .reject(&:empty?)
72
78
  end
73
79
 
74
80
  def bash
@@ -23,12 +23,12 @@ module Gitflash
23
23
  super(message, collection, default_select_options(options))
24
24
  end
25
25
 
26
- def proceed_with_warning(message, &block)
26
+ def proceed_with_warning(message)
27
27
  raise ArgumentError, 'A block is required for this method' unless block_given?
28
28
 
29
29
  warn(message)
30
30
  answer = yes?('Do you want to proceed?')
31
- answer ? block.call : 'Exited'
31
+ answer ? yield : 'Exited'
32
32
  end
33
33
 
34
34
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gitflash
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,29 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitflash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kleanthis Mitsioulis
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-10-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: thor
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.4'
19
+ - - "<"
18
20
  - !ruby/object:Gem::Version
19
- version: '1.2'
21
+ version: '2'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
- - - "~>"
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '1.4'
29
+ - - "<"
25
30
  - !ruby/object:Gem::Version
26
- version: '1.2'
31
+ version: '2'
27
32
  - !ruby/object:Gem::Dependency
28
33
  name: tty-prompt
29
34
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +57,6 @@ dependencies:
52
57
  - - "~>"
53
58
  - !ruby/object:Gem::Version
54
59
  version: '2.6'
55
- - !ruby/object:Gem::Dependency
56
- name: rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '13.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '13.0'
69
60
  description: This gem allows you to perform a number of git commands using interactive
70
61
  cli prompts
71
62
  email:
@@ -76,16 +67,16 @@ extensions: []
76
67
  extra_rdoc_files:
77
68
  - README.md
78
69
  files:
70
+ - CHANGELOG.md
79
71
  - LICENSE
80
72
  - README.md
81
- - bin/console
82
73
  - bin/gitflash
83
- - bin/setup
84
74
  - command_descriptions.yml
85
75
  - lib/gitflash.rb
86
76
  - lib/gitflash/cli.rb
87
77
  - lib/gitflash/configuration.rb
88
78
  - lib/gitflash/git/bash_command.rb
79
+ - lib/gitflash/git/command_error.rb
89
80
  - lib/gitflash/git/wrapper.rb
90
81
  - lib/gitflash/prompt.rb
91
82
  - lib/gitflash/version.rb
@@ -93,11 +84,9 @@ homepage: https://github.com/Kleanthismits/git_flash
93
84
  licenses:
94
85
  - MIT
95
86
  metadata:
96
- homepage_uri: https://github.com/Kleanthismits/git_flash
97
87
  source_code_uri: https://github.com/Kleanthismits/git_flash
98
88
  changelog_uri: https://github.com/Kleanthismits/git_flash/blob/main/CHANGELOG.md
99
89
  rubygems_mfa_required: 'true'
100
- post_install_message:
101
90
  rdoc_options: []
102
91
  require_paths:
103
92
  - lib
@@ -105,15 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
94
  requirements:
106
95
  - - ">="
107
96
  - !ruby/object:Gem::Version
108
- version: '3.0'
97
+ version: '3.3'
109
98
  required_rubygems_version: !ruby/object:Gem::Requirement
110
99
  requirements:
111
100
  - - ">="
112
101
  - !ruby/object:Gem::Version
113
102
  version: '0'
114
103
  requirements: []
115
- rubygems_version: 3.5.9
116
- signing_key:
104
+ rubygems_version: 3.7.2
117
105
  specification_version: 4
118
106
  summary: Simplified usage of some git cli commands
119
107
  test_files: []
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'gitflash'
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require 'irb'
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here