geet 0.31.1 → 0.32.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: bf5b6b46f33b00c38e4f383c186ca62d9a196f3516cee7e5569cabc087e028be
4
- data.tar.gz: eb3464d9583e1a9672bba43752ba56707cd1ab84931e9df830c74cadbebe266d
3
+ metadata.gz: 8bb6a30bfcec73ad05020727ad03f85e828059db04e4691050d6a5d715c44640
4
+ data.tar.gz: e4e1b9b5befce16e5d7bfdce45c7b135037b391f81b3d022baf095efc077a7ea
5
5
  SHA512:
6
- metadata.gz: 983a7f8a246f87ecacd86b2afd8dd0aa6e9deaff51b443ecc29697c91b2b8c3fcb9fee462ef295fe66a526e527111e0cf0bace6b7294c38353223481df5a3d06
7
- data.tar.gz: 6e55131d0d846d6d72c9d5e1312c40291bb996313c3106e33cc1e3069c82464d6b0119eacd4f037453e9dba89b6f620d0179a65cb6adbc0248c53dd959289943
6
+ metadata.gz: 9c723d2004d953f770e7d784f211e48608778b5cf1581c73ee0413cbcdb0888291738da0c2dde348cf694791a4fae785b4c5a2aec2cbc290302064410b3677fd
7
+ data.tar.gz: a57207c68b822d4d8ffc24aa0163b630af9600ce19645365b9c75457989785ccc774fa227ae51382120d378ada0b83be9d81cab0ae5a25d33549141cc2a18a6b
data/bin/geet CHANGED
@@ -30,7 +30,11 @@ class GeetLauncher
30
30
  return
31
31
  end
32
32
 
33
- repository = Git::Repository.new(upstream: !!options[:upstream], protected_repositories: protected_repositories)
33
+ repository = Git::Repository.new(
34
+ upstream: !!options[:upstream],
35
+ protected_repositories: protected_repositories,
36
+ api_token_required: !TOKEN_OPTIONAL_COMMANDS.include?(command)
37
+ )
34
38
 
35
39
  case command
36
40
  when GIST_CREATE_COMMAND
data/geet.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.required_ruby_version = ">= 3.3.0"
10
10
  s.authors = ["Saverio Miroddi"]
11
- s.date = "2026-07-24"
11
+ s.date = "2026-09-04"
12
12
  s.email = ["saverio.pub2@gmail.com"]
13
13
  s.homepage = "https://github.com/saveriomiroddi/geet"
14
14
  s.summary = "Commandline interface for performing SCM host operations, eg. create a PR on GitHub"
@@ -20,6 +20,13 @@ module Geet
20
20
  REPO_ADD_UPSTREAM_COMMAND = T.let("repo.add_upstream", String)
21
21
  REPO_CREATE_COMMAND = T.let("repo.create", String)
22
22
  REPO_OPEN_COMMAND = T.let("repo.open", String)
23
+
24
+ TOKEN_OPTIONAL_COMMANDS = T.let([
25
+ LABEL_LIST_COMMAND,
26
+ MILESTONE_LIST_COMMAND,
27
+ PR_LIST_COMMAND,
28
+ PR_OPEN_COMMAND,
29
+ ].freeze, T::Array[String])
23
30
  end
24
31
  end
25
32
  end
@@ -23,14 +23,15 @@ module Geet
23
23
  upstream: T::Boolean,
24
24
  git_client: Utils::GitClient,
25
25
  warnings: T::Boolean, # disable all the warnings
26
- protected_repositories: T::Array[String] # warn when creating an issue/pr on these repos (format: `owner/repo`)
26
+ protected_repositories: T::Array[String], # warn when creating an issue/pr on these repos (format: `owner/repo`)
27
+ api_token_required: T::Boolean
27
28
  )
28
29
  .void
29
30
  }
30
- def initialize(upstream: false, git_client: DEFAULT_GIT_CLIENT, warnings: true, protected_repositories: [])
31
+ def initialize(upstream: false, git_client: DEFAULT_GIT_CLIENT, warnings: true, protected_repositories: [], api_token_required: true)
31
32
  @upstream = upstream
32
33
  @git_client = git_client
33
- @api_token = T.let(extract_env_api_token, String)
34
+ @api_token = T.let(extract_env_api_token(required: api_token_required), T.nilable(String))
34
35
  @warnings = warnings
35
36
  @protected_repositories = protected_repositories
36
37
  end
@@ -200,9 +201,12 @@ module Geet
200
201
  @api_interface ||= Github::ApiInterface.new(@api_token, repo_path: @git_client.path(upstream: @upstream), upstream: @upstream)
201
202
  end
202
203
 
203
- sig { returns(String) }
204
- def extract_env_api_token
205
- ENV["GH_TOKEN"] || raise("GH_TOKEN not set!")
204
+ sig { params(required: T::Boolean).returns(T.nilable(String)) }
205
+ def extract_env_api_token(required:)
206
+ api_token = ENV["GH_TOKEN"]
207
+ raise "GH_TOKEN not set!" if required && api_token.nil?
208
+
209
+ api_token
206
210
  end
207
211
  end
208
212
  end
@@ -22,13 +22,13 @@ module Geet
22
22
  #
23
23
  sig {
24
24
  params(
25
- api_token: String,
25
+ api_token: T.nilable(String),
26
26
  repo_path: T.nilable(String),
27
27
  upstream: T.nilable(T::Boolean)
28
28
  ).void
29
29
  }
30
30
  def initialize(api_token, repo_path: nil, upstream: nil)
31
- @api_token = T.let(api_token, String)
31
+ @api_token = T.let(api_token, T.nilable(String))
32
32
  @repository_path = T.let(repo_path, T.nilable(String))
33
33
  @upstream = T.let(upstream, T.nilable(T::Boolean))
34
34
  end
@@ -110,7 +110,7 @@ module Geet
110
110
 
111
111
  Net::HTTP.start(uri.host, use_ssl: true) do |http|
112
112
  request = Net::HTTP::Post.new(uri).tap do
113
- it.basic_auth API_AUTH_USER, @api_token
113
+ it.basic_auth API_AUTH_USER, @api_token if @api_token
114
114
  it["Accept"] = "application/vnd.github.v3+json"
115
115
  it.body = {query:, variables:}.to_json
116
116
  end
@@ -169,7 +169,7 @@ module Geet
169
169
  Net::HTTP.start(uri.host, use_ssl: true) do |http|
170
170
  request = http_class.new(uri)
171
171
 
172
- request.basic_auth API_AUTH_USER, @api_token
172
+ request.basic_auth API_AUTH_USER, @api_token if @api_token
173
173
  request.body = data.to_json if data
174
174
  request["Accept"] = "application/vnd.github.v3+json"
175
175
 
@@ -26,11 +26,7 @@ module Geet
26
26
  #
27
27
  sig { returns(Github::PR) }
28
28
  def checked_find_branch_pr
29
- owner = if @repository.upstream?
30
- @repository.authenticated_user.username
31
- else
32
- @git_client.owner
33
- end
29
+ owner = @git_client.owner
34
30
 
35
31
  head = @git_client.current_branch
36
32
 
data/lib/geet/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: strict
3
3
 
4
4
  module Geet
5
- VERSION = "0.31.1"
5
+ VERSION = "0.32.0"
6
6
  end
@@ -39,4 +39,27 @@ describe Geet::Services::OpenPr do
39
39
  end
40
40
 
41
41
  end # context 'with github.com'
42
+
43
+ context "when opening an upstream PR" do
44
+ let(:repository) { instance_double(Geet::Git::Repository, upstream?: true) }
45
+
46
+ it "uses the origin owner without requesting the authenticated user" do
47
+ pr = instance_double(
48
+ Geet::Github::PR,
49
+ link: "https://github.com/upstream/repository/pull/1"
50
+ )
51
+
52
+ allow(git_client).to receive(:owner).and_return(owner)
53
+ allow(git_client).to receive(:current_branch).and_return(branch)
54
+ allow(repository).to receive(:prs).with(owner:, head: branch).and_return([pr])
55
+ expect(repository).not_to receive(:authenticated_user)
56
+
57
+ expect {
58
+ service_instance = described_class.new(repository, git_client: git_client)
59
+ allow(service_instance).to receive(:open_file_with_default_application)
60
+
61
+ expect(service_instance.execute).to equal(pr)
62
+ }.to output("Finding PR with head (#{owner}:#{branch})...\n").to_stdout
63
+ end
64
+ end
42
65
  end
@@ -45,5 +45,46 @@ describe GeetLauncher do
45
45
  ensure
46
46
  ENV["GH_TOKEN"] = without_token if without_token
47
47
  end
48
+
49
+ {
50
+ Geet::Commandline::Commands::LABEL_LIST_COMMAND => Geet::Services::ListLabels,
51
+ Geet::Commandline::Commands::MILESTONE_LIST_COMMAND => Geet::Services::ListMilestones,
52
+ Geet::Commandline::Commands::PR_LIST_COMMAND => Geet::Services::ListPrs,
53
+ Geet::Commandline::Commands::PR_OPEN_COMMAND => Geet::Services::OpenPr,
54
+ }.each do |command, service_class|
55
+ it "dispatches #{command} with an optional API token" do
56
+ launcher = described_class.new
57
+ repository = instance_double(Geet::Git::Repository)
58
+ service = instance_double(service_class)
59
+
60
+ allow_any_instance_of(Geet::Commandline::Configuration).to receive(:decode_argv).and_return([command, {}])
61
+ allow(launcher).to receive(:protected_repositories).and_return([])
62
+ expect(Geet::Git::Repository).to receive(:new).with(
63
+ upstream: false,
64
+ protected_repositories: [],
65
+ api_token_required: false
66
+ ).and_return(repository)
67
+ expect(service_class).to receive(:new).with(repository).and_return(service)
68
+ expect(service).to receive(:execute).with(no_args)
69
+
70
+ launcher.launch
71
+ end
72
+ end
73
+
74
+ it "requires an API token for issue.list" do
75
+ launcher = described_class.new
76
+
77
+ allow_any_instance_of(Geet::Commandline::Configuration).to receive(:decode_argv).and_return([
78
+ Geet::Commandline::Commands::ISSUE_LIST_COMMAND,
79
+ {},
80
+ ])
81
+ allow(launcher).to receive(:protected_repositories).and_return([])
82
+ expect(Geet::Services::ListIssues).not_to receive(:new)
83
+
84
+ api_token = ENV.delete("GH_TOKEN")
85
+ expect { launcher.launch }.to raise_error("GH_TOKEN not set!")
86
+ ensure
87
+ ENV["GH_TOKEN"] = api_token if api_token
88
+ end
48
89
  end
49
90
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Geet::Git::Repository do
6
+ describe "API token handling" do
7
+ it "allows the token to be omitted when authentication is not required" do
8
+ api_token = ENV.delete("GH_TOKEN")
9
+
10
+ expect { described_class.new(api_token_required: false) }.not_to raise_error
11
+ ensure
12
+ ENV["GH_TOKEN"] = api_token if api_token
13
+ end
14
+
15
+ it "requires the token by default" do
16
+ api_token = ENV.delete("GH_TOKEN")
17
+
18
+ expect { described_class.new }.to raise_error("GH_TOKEN not set!")
19
+ ensure
20
+ ENV["GH_TOKEN"] = api_token if api_token
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "webmock/rspec"
5
+
6
+ describe Geet::Github::ApiInterface do
7
+ describe "authentication" do
8
+ let(:request_url) { "https://api.github.com/repos/owner/repository/issues" }
9
+
10
+ it "sends an unauthenticated request when no token is available" do
11
+ request = stub_request(:get, request_url)
12
+ .with { |actual_request| !actual_request.headers.key?("Authorization") }
13
+ .to_return(status: 200, body: "[]")
14
+
15
+ described_class.new(nil, repo_path: "owner/repository").send_request("issues")
16
+
17
+ expect(request).to have_been_requested.once
18
+ end
19
+
20
+ it "authenticates the request when a token is available" do
21
+ authorization = "Basic #{Base64.strict_encode64(":token")}"
22
+ request = stub_request(:get, request_url)
23
+ .with(headers: {"Authorization" => authorization})
24
+ .to_return(status: 200, body: "[]")
25
+
26
+ described_class.new("token", repo_path: "owner/repository").send_request("issues")
27
+
28
+ expect(request).to have_been_requested.once
29
+ end
30
+ end
31
+ end
@@ -7,6 +7,32 @@ require_relative "../../../lib/geet/github/pr"
7
7
  require_relative "../../../lib/geet/github/api_interface"
8
8
 
9
9
  describe Geet::Github::PR do
10
+ describe ".list" do
11
+ it "filters upstream PRs by the supplied origin owner and branch" do
12
+ api_interface = instance_double(Geet::Github::ApiInterface, upstream?: true)
13
+ matching_pr = {
14
+ "number" => 1,
15
+ "title" => "Matching PR",
16
+ "html_url" => "https://github.com/upstream/repository/pull/1",
17
+ "head" => {"label" => "origin-owner:topic"},
18
+ }
19
+ other_pr = {
20
+ "number" => 2,
21
+ "title" => "Other PR",
22
+ "html_url" => "https://github.com/upstream/repository/pull/2",
23
+ "head" => {"label" => "other-owner:topic"},
24
+ }
25
+
26
+ expect(api_interface).to receive(:send_request)
27
+ .with("pulls", multipage: true)
28
+ .and_return([matching_pr, other_pr])
29
+
30
+ prs = described_class.list(api_interface, owner: "origin-owner", head: "topic")
31
+
32
+ expect(prs.map(&:number)).to eq([1])
33
+ end
34
+ end
35
+
10
36
  describe "#fetch_available_merge_method" do
11
37
  let(:api_interface) { instance_double(Geet::Github::ApiInterface) }
12
38
  let(:pr_number) { 123 }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.1
4
+ version: 0.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-24 00:00:00.000000000 Z
10
+ date: 2026-09-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -260,6 +260,8 @@ files:
260
260
  - spec/spec_helper.rb
261
261
  - spec/unit/commandline/configuration_spec.rb
262
262
  - spec/unit/geet_launcher_spec.rb
263
+ - spec/unit/git/repository_spec.rb
264
+ - spec/unit/github/api_interface_spec.rb
263
265
  - spec/unit/github/pr_spec.rb
264
266
  - spec/unit/github/repository_spec.rb
265
267
  - spec/unit/services/create_repo_spec.rb