geet 0.30.1 → 0.31.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 +4 -4
- data/.github/workflows/ci.yml +2 -2
- data/.ruby-version +1 -1
- data/README.md +17 -1
- data/Rakefile +1 -0
- data/bin/geet +11 -3
- data/docs/superpowers/plans/2026-07-12-pre-existing-build-and-ci-fixes.md +89 -0
- data/docs/superpowers/plans/2026-07-12-repo-create.md +435 -0
- data/docs/superpowers/plans/2026-07-12-ruby-4-upgrade.md +111 -0
- data/docs/superpowers/specs/2026-07-12-pre-existing-build-and-ci-fixes-design.md +15 -0
- data/docs/superpowers/specs/2026-07-12-repo-create-design.md +43 -0
- data/docs/superpowers/specs/2026-07-12-ruby-4-upgrade-design.md +17 -0
- data/geet.gemspec +3 -3
- data/lib/geet/commandline/commands.rb +1 -0
- data/lib/geet/commandline/configuration.rb +7 -0
- data/lib/geet/github/repository.rb +88 -0
- data/lib/geet/services/create_repo.rb +108 -0
- data/lib/geet/services/open_repo.rb +2 -4
- data/lib/geet/utils/git_client.rb +15 -4
- data/lib/geet/version.rb +1 -1
- data/sorbet/rbi/gems/{rake@12.3.3.rbi → rake@13.4.2.rbi} +724 -486
- data/spec/integration/open_repo_spec.rb +1 -2
- data/spec/unit/commandline/configuration_spec.rb +34 -0
- data/spec/unit/geet_launcher_spec.rb +38 -0
- data/spec/unit/github/repository_spec.rb +40 -0
- data/spec/unit/services/create_repo_spec.rb +187 -0
- data/spec/unit/utils/git_client_spec.rb +50 -0
- metadata +19 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 959d270dbe83292c276b920e22995c6a901cfe149be68595d35ab8917dc9b527
|
|
4
|
+
data.tar.gz: 5666cf9e1f33a5d8d09aed56049d4839f13daf984894ce4fc6acf4d2527ae7b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 316d184df8c655265eea18be05c60a29bd22eb5c90b4c3e6f7d97a737e84f3d95e861dc7744a7a1d19473dff74c41549af719c8976f4139c87085d82b947203c
|
|
7
|
+
data.tar.gz: d44f2acbb7fb8f33fa611bacd59b3ebe4001739bdfc1a10497c4fe36f3de4a4e53cc642809d4547be9837c97df56eb6650796f40dcf82c6baeec0559947ff6c9
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -7,10 +7,10 @@ jobs:
|
|
|
7
7
|
name: Test Suite
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
env:
|
|
10
|
-
|
|
10
|
+
GH_TOKEN: foo
|
|
11
11
|
strategy:
|
|
12
12
|
matrix:
|
|
13
|
-
ruby-version: [head, 4.0, 3.4, 3.3
|
|
13
|
+
ruby-version: [head, 4.0, 3.4, 3.3]
|
|
14
14
|
fail-fast: false
|
|
15
15
|
continue-on-error: ${{ endsWith(matrix['ruby-version'], 'head') }}
|
|
16
16
|
steps:
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4.0.5
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Everything is tracked in detail via [issues](https://github.com/saveriomiroddi/g
|
|
|
12
12
|
|
|
13
13
|
## Supported operations
|
|
14
14
|
|
|
15
|
-
- create label, gist, issue, milestone, PR
|
|
15
|
+
- create label, gist, issue, milestone, PR, repository, repository fork
|
|
16
16
|
- list issues, labels, milestones, PRs
|
|
17
17
|
- merge PR, comment PR
|
|
18
18
|
- open repository
|
|
@@ -27,6 +27,22 @@ Geet requires the API token environment variable to be set, eg:
|
|
|
27
27
|
|
|
28
28
|
All the commands need to be run from the git repository.
|
|
29
29
|
|
|
30
|
+
### Create a repository or fork
|
|
31
|
+
|
|
32
|
+
Create a repository and select its visibility interactively:
|
|
33
|
+
|
|
34
|
+
$ geet repo create
|
|
35
|
+
|
|
36
|
+
Create a private repository without a prompt:
|
|
37
|
+
|
|
38
|
+
$ geet repo create --visibility private
|
|
39
|
+
|
|
40
|
+
Create a fork while preserving the source as `upstream`:
|
|
41
|
+
|
|
42
|
+
$ geet repo create --upstream git@github.com:owner/project.git
|
|
43
|
+
|
|
44
|
+
Geet configures the created repository's SSH address as `origin`, preserves the source address as `upstream` when forking, pushes the current branch, and updates the GitHub default branch to match it.
|
|
45
|
+
|
|
30
46
|
### Create an issue (with label and assignees)
|
|
31
47
|
|
|
32
48
|
Basic creation of an issue:
|
data/Rakefile
CHANGED
data/bin/geet
CHANGED
|
@@ -16,6 +16,16 @@ class GeetLauncher
|
|
|
16
16
|
def launch
|
|
17
17
|
command, options = Commandline::Configuration.new.decode_argv || exit
|
|
18
18
|
|
|
19
|
+
if command == REPO_CREATE_COMMAND
|
|
20
|
+
Services::CreateRepo.new.execute(**options)
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if command == REPO_OPEN_COMMAND
|
|
25
|
+
Services::OpenRepo.new.execute(**options)
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
repository = Git::Repository.new(upstream: !!options[:upstream], protected_repositories: protected_repositories)
|
|
20
30
|
|
|
21
31
|
case command
|
|
@@ -75,8 +85,6 @@ class GeetLauncher
|
|
|
75
85
|
Services::OpenPr.new(repository).execute(**options)
|
|
76
86
|
when REPO_ADD_UPSTREAM_COMMAND
|
|
77
87
|
Services::AddUpstreamRepo.new(repository).execute
|
|
78
|
-
when REPO_OPEN_COMMAND
|
|
79
|
-
Services::OpenRepo.new(repository).execute(**options)
|
|
80
88
|
else
|
|
81
89
|
raise "Internal error - Unrecognized command #{command.inspect}"
|
|
82
90
|
end
|
|
@@ -129,4 +137,4 @@ class GeetLauncher
|
|
|
129
137
|
end
|
|
130
138
|
end
|
|
131
139
|
|
|
132
|
-
GeetLauncher.new.launch
|
|
140
|
+
GeetLauncher.new.launch if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Pre-existing Build and CI Fixes Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Align CI with the project's `GH_TOKEN` contract and expose Bundler's standard gem build tasks through Rake.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Correct the existing configuration at its two sources: the CI environment declaration and the Rake task imports. Preserve the application's established token interface and use Bundler's provided gem tasks without custom wrappers.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby 4.0.5, Rake, Bundler, GitHub Actions, RSpec, Sorbet, RuboCop
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- CI supplies the token as `GH_TOKEN`.
|
|
14
|
+
- `Rakefile` loads `bundler/gem_tasks`.
|
|
15
|
+
- Do not add token aliases, fallback credentials, dependency updates, or unrelated build changes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
### Task 1: Correct the CI token and Rake gem tasks
|
|
20
|
+
|
|
21
|
+
**Files:**
|
|
22
|
+
- Modify: `.github/workflows/ci.yml`
|
|
23
|
+
- Modify: `Rakefile`
|
|
24
|
+
|
|
25
|
+
**Interfaces:**
|
|
26
|
+
- Consumes: the application's established `GH_TOKEN` environment-variable contract and Bundler's `gem_tasks` API
|
|
27
|
+
- Produces: a CI test job that satisfies the test harness and standard `rake build` and release tasks
|
|
28
|
+
|
|
29
|
+
- [ ] **Step 1: Demonstrate both pre-existing failures**
|
|
30
|
+
|
|
31
|
+
Run:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
env -u GH_TOKEN GITHUB_API_TOKEN=foo bundle exec rspec \
|
|
35
|
+
&& exit 1 \
|
|
36
|
+
|| test "$?" -eq 1
|
|
37
|
+
bundle exec rake build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Expected: RSpec fails because `GH_TOKEN` is absent, and Rake fails with `Don't know how to build task 'build'`.
|
|
41
|
+
|
|
42
|
+
- [ ] **Step 2: Correct the configuration**
|
|
43
|
+
|
|
44
|
+
In `.github/workflows/ci.yml`, set the test-suite environment to:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
env:
|
|
48
|
+
GH_TOKEN: foo
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
At the beginning of `Rakefile`, add:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require "bundler/gem_tasks"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- [ ] **Step 3: Run focused verification**
|
|
58
|
+
|
|
59
|
+
Run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
GH_TOKEN=foo bundle exec rspec \
|
|
63
|
+
&& bundle exec rake build
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Expected: 39 examples pass with no failures, and `pkg/geet-0.30.1.gem` is built.
|
|
67
|
+
|
|
68
|
+
- [ ] **Step 4: Run full verification and clean generated output**
|
|
69
|
+
|
|
70
|
+
Run:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
GH_TOKEN=foo bundle exec rake \
|
|
74
|
+
&& bundle exec srb typecheck \
|
|
75
|
+
&& bundle exec rubocop \
|
|
76
|
+
&& rm -f pkg/geet-0.30.1.gem \
|
|
77
|
+
&& rmdir pkg \
|
|
78
|
+
&& git diff --check \
|
|
79
|
+
&& git status --short
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Expected: the default Rake task passes 39 examples, Sorbet reports no errors, RuboCop reports no offenses, generated packaging output is removed, and only `.github/workflows/ci.yml` and `Rakefile` are modified.
|
|
83
|
+
|
|
84
|
+
- [ ] **Step 5: Commit the fixes**
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git add .github/workflows/ci.yml Rakefile \
|
|
88
|
+
&& git commit -m "Fix CI token and gem build tasks"
|
|
89
|
+
```
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Repository Creation Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add `geet repo create` to create or fork a GitHub repository from the current directory, configure remotes, push the current branch, and make it the default branch.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A new `Services::CreateRepo` owns the workflow without constructing `Git::Repository`. A focused `Github::Repository` models repository API operations, while `Utils::GitClient` provides the two missing local queries. The existing command decoder and launcher expose the service.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby 4.0, Sorbet runtime signatures, RSpec, WebMock/VCR-compatible GitHub REST calls, TTY::Prompt, Git CLI.
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- Work only in `/home/saverio/code/geet-repo-create` on branch `repo-create`.
|
|
14
|
+
- The repository name is the basename of the current directory.
|
|
15
|
+
- Generate `origin` as `git@github.com:OWNER/REPOSITORY.git`.
|
|
16
|
+
- Preserve the supplied `--upstream` address unchanged for the local `upstream` remote.
|
|
17
|
+
- Reject an existing `origin`; in fork mode also reject an existing `upstream`.
|
|
18
|
+
- Reject `--visibility` with `--upstream`; otherwise accept only `private` or `public` and prompt when omitted.
|
|
19
|
+
- Push the current branch with upstream tracking before setting it as GitHub's default branch.
|
|
20
|
+
- Do not roll back a created GitHub repository or configured remote after a later failure.
|
|
21
|
+
- Run RSpec with `GH_TOKEN=test` so recorded integration tests can initialize their API clients.
|
|
22
|
+
- Do not use conventional-commit prefixes in commit titles.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### Task 1: GitHub repository API object
|
|
27
|
+
|
|
28
|
+
**Files:**
|
|
29
|
+
- Create: `lib/geet/github/repository.rb`
|
|
30
|
+
- Create: `spec/unit/github/repository_spec.rb`
|
|
31
|
+
|
|
32
|
+
**Interfaces:**
|
|
33
|
+
- Consumes: `Geet::Github::ApiInterface#send_request(api_path, data:, http_method:)`.
|
|
34
|
+
- Produces: `Github::Repository.create(name, visibility, api_interface)`, `.fork(upstream_path, name, api_interface)`, `.fetch(path, api_interface)`, and `#update_default_branch(branch)`; instances expose `full_name`, `html_url`, and `ssh_url`.
|
|
35
|
+
|
|
36
|
+
- [ ] **Step 1: Write failing API-object specs**
|
|
37
|
+
|
|
38
|
+
Create `spec/unit/github/repository_spec.rb` with examples that use an `instance_double(Geet::Github::ApiInterface)` and assert these exact requests and results:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# frozen_string_literal: true
|
|
42
|
+
|
|
43
|
+
require_relative "../../spec_helper"
|
|
44
|
+
|
|
45
|
+
RSpec.describe Geet::Github::Repository do
|
|
46
|
+
let(:api_interface) { instance_double(Geet::Github::ApiInterface) }
|
|
47
|
+
let(:response) do
|
|
48
|
+
{
|
|
49
|
+
"full_name" => "donald/geet",
|
|
50
|
+
"html_url" => "https://github.com/donald/geet",
|
|
51
|
+
"ssh_url" => "git@github.com:donald/geet.git",
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "creates a private repository for the authenticated user" do
|
|
56
|
+
expect(api_interface).to receive(:send_request).with("/user/repos", data: {name: "geet", private: true}).and_return(response)
|
|
57
|
+
repository = described_class.create("geet", "private", api_interface)
|
|
58
|
+
expect([repository.full_name, repository.html_url, repository.ssh_url]).to eq(["donald/geet", "https://github.com/donald/geet", "git@github.com:donald/geet.git"])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "creates a public repository for the authenticated user" do
|
|
62
|
+
expect(api_interface).to receive(:send_request).with("/user/repos", data: {name: "geet", private: false}).and_return(response)
|
|
63
|
+
described_class.create("geet", "public", api_interface)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "requests a named fork" do
|
|
67
|
+
expect(api_interface).to receive(:send_request).with("/repos/upstream/project/forks", data: {name: "geet"}).and_return(response)
|
|
68
|
+
described_class.fork("upstream/project", "geet", api_interface)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "fetches a repository" do
|
|
72
|
+
expect(api_interface).to receive(:send_request).with("/repos/donald/geet").and_return(response)
|
|
73
|
+
expect(described_class.fetch("donald/geet", api_interface).full_name).to eq("donald/geet")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "updates the default branch" do
|
|
77
|
+
expect(api_interface).to receive(:send_request).with("/repos/donald/geet", data: {default_branch: "topic"}, http_method: :patch)
|
|
78
|
+
described_class.new(response, api_interface).update_default_branch("topic")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 2: Run the specs and verify the missing constant failure**
|
|
84
|
+
|
|
85
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/github/repository_spec.rb`
|
|
86
|
+
|
|
87
|
+
Expected: failure because `Geet::Github::Repository` is not defined.
|
|
88
|
+
|
|
89
|
+
- [ ] **Step 3: Implement the repository API object**
|
|
90
|
+
|
|
91
|
+
Create `lib/geet/github/repository.rb` with strict Sorbet signatures, readers for the three response strings, a constructor that extracts them with `fetch`, the three class methods using the exact API paths above, and:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
sig { params(branch: String).void }
|
|
95
|
+
def update_default_branch(branch)
|
|
96
|
+
@api_interface.send_request("/repos/#{@full_name}", data: {default_branch: branch}, http_method: :patch)
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use a private `from_response` class helper so `create`, `fork`, and `fetch` each cast the response once and construct the object without duplicating extraction logic.
|
|
101
|
+
|
|
102
|
+
- [ ] **Step 4: Run the API-object specs**
|
|
103
|
+
|
|
104
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/github/repository_spec.rb`
|
|
105
|
+
|
|
106
|
+
Expected: 5 examples, 0 failures.
|
|
107
|
+
|
|
108
|
+
- [ ] **Step 5: Commit the API object**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git add lib/geet/github/repository.rb spec/unit/github/repository_spec.rb
|
|
112
|
+
git commit -m "Add GitHub repository API operations"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Task 2: Local Git preflight queries and address parsing
|
|
116
|
+
|
|
117
|
+
**Files:**
|
|
118
|
+
- Modify: `lib/geet/utils/git_client.rb`
|
|
119
|
+
- Create: `spec/unit/utils/git_client_spec.rb`
|
|
120
|
+
|
|
121
|
+
**Interfaces:**
|
|
122
|
+
- Consumes: `Utils::GitClient#execute_git_command` and the existing `REMOTE_URL_REGEX`.
|
|
123
|
+
- Produces: `Utils::GitClient#head_commit? -> T::Boolean` and `#remote_path(remote_url: String) -> String`.
|
|
124
|
+
|
|
125
|
+
- [ ] **Step 1: Write failing GitClient specs**
|
|
126
|
+
|
|
127
|
+
Create `spec/unit/utils/git_client_spec.rb`. Instantiate `GitClient`, stub its private `execute_git_command`, and cover:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
describe "#head_commit?" do
|
|
131
|
+
it "returns true when HEAD resolves" do
|
|
132
|
+
allow(subject).to receive(:execute_git_command).with("rev-parse --verify HEAD", allow_error: true, silent_stderr: true).and_return("abc123")
|
|
133
|
+
expect(subject.head_commit?).to be(true)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "returns false for an unborn HEAD" do
|
|
137
|
+
allow(subject).to receive(:execute_git_command).with("rev-parse --verify HEAD", allow_error: true, silent_stderr: true).and_return("")
|
|
138
|
+
expect(subject.head_commit?).to be(false)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe "#remote_path" do
|
|
143
|
+
it "extracts an SSH repository path" do
|
|
144
|
+
expect(subject.remote_path("git@github.com:owner/project.git")).to eq("owner/project")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "extracts an HTTPS repository path" do
|
|
148
|
+
expect(subject.remote_path("https://github.com/owner/project.git")).to eq("owner/project")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "rejects an unsupported address" do
|
|
152
|
+
expect { subject.remote_path("owner/project") }.to raise_error(RuntimeError, 'Unexpected remote reference format: "owner/project"')
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- [ ] **Step 2: Run the specs and verify missing-method failures**
|
|
158
|
+
|
|
159
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/utils/git_client_spec.rb`
|
|
160
|
+
|
|
161
|
+
Expected: failures reporting undefined `head_commit?` and `remote_path`.
|
|
162
|
+
|
|
163
|
+
- [ ] **Step 3: Implement the two queries and reuse address parsing**
|
|
164
|
+
|
|
165
|
+
Add:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
sig { returns(T::Boolean) }
|
|
169
|
+
def head_commit?
|
|
170
|
+
execute_git_command("rev-parse --verify HEAD", allow_error: true, silent_stderr: true) != ""
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
sig { params(remote_url: String).returns(String) }
|
|
174
|
+
def remote_path(remote_url)
|
|
175
|
+
match = remote_url.match(REMOTE_URL_REGEX)
|
|
176
|
+
raise "Unexpected remote reference format: #{remote_url.inspect}" if !match
|
|
177
|
+
T.must(match[4])
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Change `#path` to call `remote_path(remote(**remote_name_option))`, and change `#remote` to validate by calling `remote_path(remote_url)` after the existing missing-remote check. Do not expand the accepted URL grammar in this task.
|
|
182
|
+
|
|
183
|
+
- [ ] **Step 4: Run focused and full tests**
|
|
184
|
+
|
|
185
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/utils/git_client_spec.rb spec/integration/open_repo_spec.rb`
|
|
186
|
+
|
|
187
|
+
Expected: 7 examples, 0 failures.
|
|
188
|
+
|
|
189
|
+
Run: `GH_TOKEN=test bundle exec rake`
|
|
190
|
+
|
|
191
|
+
Expected: all examples pass.
|
|
192
|
+
|
|
193
|
+
- [ ] **Step 5: Commit the Git primitives**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git add lib/geet/utils/git_client.rb spec/unit/utils/git_client_spec.rb
|
|
197
|
+
git commit -m "Add repository creation Git queries"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Task 3: Repository creation workflow
|
|
201
|
+
|
|
202
|
+
**Files:**
|
|
203
|
+
- Create: `lib/geet/services/create_repo.rb`
|
|
204
|
+
- Create: `spec/unit/services/create_repo_spec.rb`
|
|
205
|
+
|
|
206
|
+
**Interfaces:**
|
|
207
|
+
- Consumes: Task 1's `Github::Repository` methods; Task 2's `GitClient#head_commit?` and `#remote_path`; existing `#current_branch`, `#remote_defined?`, `#add_remote`, and `#push`.
|
|
208
|
+
- Produces: `Services::CreateRepo#execute(visibility: T.nilable(String), upstream: T.nilable(String)) -> Github::Repository`.
|
|
209
|
+
|
|
210
|
+
- [ ] **Step 1: Write failing preflight and visibility specs**
|
|
211
|
+
|
|
212
|
+
Build `spec/unit/services/create_repo_spec.rb` around doubles for `GitClient`, `Github::ApiInterface`, and `TTY::Prompt`. Construct the service with injectable `directory: "/tmp/geet"`, `out: StringIO.new`, `git_client:`, `api_interface:`, `prompt:`, `sleeper: ->(_) {}` and `fork_attempts: 3`.
|
|
213
|
+
|
|
214
|
+
Add examples asserting that no API request occurs and the exact errors are raised for:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
"The repository has no commits!"
|
|
218
|
+
"Remote \"origin\" already exists!"
|
|
219
|
+
"Remote \"upstream\" already exists!"
|
|
220
|
+
"Visibility must be private or public!"
|
|
221
|
+
"Visibility can't be specified with upstream!"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The common valid preflight stubs are `head_commit? => true`, `current_branch => "topic"`, `remote_defined?("origin") => false`, and, only in fork mode, `remote_defined?("upstream") => false`.
|
|
225
|
+
|
|
226
|
+
Add one example where `visibility` is nil, `prompt.select("Visibility:", ["private", "public"])` returns `"private"`, and the normal create path receives `"private"`. Add one example showing explicit `"public"` does not call the prompt.
|
|
227
|
+
|
|
228
|
+
- [ ] **Step 2: Run the preflight specs and verify the missing service failure**
|
|
229
|
+
|
|
230
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
|
|
231
|
+
|
|
232
|
+
Expected: failure because `Geet::Services::CreateRepo` is not defined.
|
|
233
|
+
|
|
234
|
+
- [ ] **Step 3: Implement initialization and preflight**
|
|
235
|
+
|
|
236
|
+
Create the strict service with these defaults and injected types:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
VISIBILITIES = T.let(["private", "public"].freeze, T::Array[String])
|
|
240
|
+
FORK_ATTEMPTS = 30
|
|
241
|
+
FORK_RETRY_DELAY = 1
|
|
242
|
+
|
|
243
|
+
def initialize(directory: Dir.pwd, out: $stdout, git_client: Utils::GitClient.new, api_interface: nil, prompt: TTY::Prompt.new, sleeper: Kernel.method(:sleep), fork_attempts: FORK_ATTEMPTS)
|
|
244
|
+
@directory = directory
|
|
245
|
+
@out = out
|
|
246
|
+
@git_client = git_client
|
|
247
|
+
@api_interface = api_interface || Github::ApiInterface.new(ENV["GH_TOKEN"] || raise("GH_TOKEN not set!"))
|
|
248
|
+
@prompt = prompt
|
|
249
|
+
@sleeper = sleeper
|
|
250
|
+
@fork_attempts = fork_attempts
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`execute` must call preflight before prompting or making API calls. Derive `name = File.basename(@directory)` and `branch = @git_client.current_branch`. Keep validation in short private methods with the exact error text from Step 1.
|
|
255
|
+
|
|
256
|
+
- [ ] **Step 4: Run preflight specs**
|
|
257
|
+
|
|
258
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
|
|
259
|
+
|
|
260
|
+
Expected: all preflight and prompt examples pass; workflow examples added next are still absent.
|
|
261
|
+
|
|
262
|
+
- [ ] **Step 5: Add failing normal-workflow and fork-workflow specs**
|
|
263
|
+
|
|
264
|
+
For normal creation, stub `Github::Repository.create("geet", "private", api_interface)` to return a repository double with `full_name: "donald/geet"`, `ssh_url: "git@github.com:donald/geet.git"`, and `html_url: "https://github.com/donald/geet"`. Assert ordered calls to add `origin`, push `"topic"`, and update the default branch.
|
|
265
|
+
|
|
266
|
+
For fork creation, use upstream `https://github.com/upstream/project.git`, assert `remote_path` returns `"upstream/project"`, assert `Github::Repository.fork("upstream/project", "geet", api_interface)` returns a provisional repository with `full_name: "donald/geet"`, then make `.fetch("donald/geet", api_interface)` first raise `Shared::HttpError.new("Not Found", 404)` and then return the available repository. Assert the sleeper receives `1`, the exact supplied URL is added as `upstream`, SSH `origin` is added, and push/default-branch calls follow.
|
|
267
|
+
|
|
268
|
+
Add a timeout example where all three fetches raise 404 and expect `"Timed out waiting for fork donald/geet"`. Add a non-404 fetch error example and expect it to propagate immediately. Add a failure-order example proving an `add_remote` error prevents push and default-branch update. Assert success output contains both `Repository address: https://github.com/donald/geet` and `Default branch: topic`.
|
|
269
|
+
|
|
270
|
+
- [ ] **Step 6: Implement normal creation, fork waiting, remotes, push, and output**
|
|
271
|
+
|
|
272
|
+
Implement the workflow in this order:
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
repository = upstream ? create_fork(name, upstream) : Github::Repository.create(name, selected_visibility, @api_interface)
|
|
276
|
+
@git_client.add_remote(Utils::GitClient::ORIGIN_NAME, repository.ssh_url)
|
|
277
|
+
@git_client.add_remote(Utils::GitClient::UPSTREAM_NAME, upstream) if upstream
|
|
278
|
+
@git_client.push(remote_branch: branch)
|
|
279
|
+
repository.update_default_branch(branch)
|
|
280
|
+
@out.puts "Repository address: #{repository.html_url}"
|
|
281
|
+
@out.puts "Default branch: #{branch}"
|
|
282
|
+
repository
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`create_fork` parses the upstream, requests the fork, then calls `wait_for_fork(full_name)`. `wait_for_fork` attempts `Github::Repository.fetch` up to `@fork_attempts` times, rescues only `Shared::HttpError` with code 404, sleeps `FORK_RETRY_DELAY` between attempts but not after the final attempt, and raises the exact timeout error after exhaustion.
|
|
286
|
+
|
|
287
|
+
- [ ] **Step 7: Run service and full tests**
|
|
288
|
+
|
|
289
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
|
|
290
|
+
|
|
291
|
+
Expected: all examples pass.
|
|
292
|
+
|
|
293
|
+
Run: `GH_TOKEN=test bundle exec rake`
|
|
294
|
+
|
|
295
|
+
Expected: all examples pass.
|
|
296
|
+
|
|
297
|
+
- [ ] **Step 8: Commit the service**
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
git add lib/geet/services/create_repo.rb spec/unit/services/create_repo_spec.rb
|
|
301
|
+
git commit -m "Add repository creation workflow"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Task 4: CLI integration and user documentation
|
|
305
|
+
|
|
306
|
+
**Files:**
|
|
307
|
+
- Modify: `lib/geet/commandline/commands.rb`
|
|
308
|
+
- Modify: `lib/geet/commandline/configuration.rb`
|
|
309
|
+
- Modify: `bin/geet`
|
|
310
|
+
- Create: `spec/unit/commandline/configuration_spec.rb`
|
|
311
|
+
- Create: `spec/unit/geet_launcher_spec.rb`
|
|
312
|
+
- Modify: `README.md`
|
|
313
|
+
|
|
314
|
+
**Interfaces:**
|
|
315
|
+
- Consumes: `Services::CreateRepo#execute(visibility:, upstream:)` from Task 3.
|
|
316
|
+
- Produces: `REPO_CREATE_COMMAND = "repo.create"` and CLI syntax `geet repo create [-v|--visibility private|public] [-u|--upstream ADDRESS]`.
|
|
317
|
+
|
|
318
|
+
- [ ] **Step 1: Write failing command-decoding specs**
|
|
319
|
+
|
|
320
|
+
In `spec/unit/commandline/configuration_spec.rb`, stub `ARGV` via `stub_const("ARGV", [...])` and assert:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
expect(described_class.new.decode_argv).to eq(["repo.create", {visibility: "private", upstream: "git@github.com:owner/project.git"}])
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Cover long options and a second example for `-v public -u https://github.com/owner/project.git` so both aliases and value parsing are exercised.
|
|
327
|
+
|
|
328
|
+
- [ ] **Step 2: Run the decoder specs and verify failure**
|
|
329
|
+
|
|
330
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb`
|
|
331
|
+
|
|
332
|
+
Expected: decoding fails because `repo.create` is not registered.
|
|
333
|
+
|
|
334
|
+
- [ ] **Step 3: Register the command and options**
|
|
335
|
+
|
|
336
|
+
Add `REPO_CREATE_COMMAND = T.let("repo.create", String)`, define:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
REPO_CREATE_OPTIONS = T.let([
|
|
340
|
+
["-v", "--visibility private|public", "Repository visibility"],
|
|
341
|
+
["-u", "--upstream address", "Create a fork of this repository"],
|
|
342
|
+
long_help: "Create a GitHub repository from the current directory.",
|
|
343
|
+
], T::Array[T.any(T::Hash[T.untyped, T.untyped], T::Array[String])])
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
and register `"create" => REPO_CREATE_OPTIONS` under `"repo"`.
|
|
347
|
+
|
|
348
|
+
- [ ] **Step 4: Run the decoder specs**
|
|
349
|
+
|
|
350
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb`
|
|
351
|
+
|
|
352
|
+
Expected: 2 examples, 0 failures.
|
|
353
|
+
|
|
354
|
+
- [ ] **Step 5: Write a failing launcher dispatch spec**
|
|
355
|
+
|
|
356
|
+
Change the final line of `bin/geet` to `GeetLauncher.new.launch if $PROGRAM_NAME == __FILE__` so the class can be required safely. In `spec/unit/geet_launcher_spec.rb`, require `../../bin/geet`, stub `Commandline::Configuration#decode_argv` to return `[REPO_CREATE_COMMAND, {visibility: "private"}]`, expect `Services::CreateRepo.new.execute(visibility: "private")`, and expect `Git::Repository` not to receive `new`. This proves creation bypasses the existing-origin abstraction.
|
|
357
|
+
|
|
358
|
+
- [ ] **Step 6: Run the launcher spec and verify dispatch failure**
|
|
359
|
+
|
|
360
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/geet_launcher_spec.rb`
|
|
361
|
+
|
|
362
|
+
Expected: failure because the launcher does not handle `REPO_CREATE_COMMAND`.
|
|
363
|
+
|
|
364
|
+
- [ ] **Step 7: Add early launcher dispatch**
|
|
365
|
+
|
|
366
|
+
Immediately after decoding arguments, add:
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
if command == REPO_CREATE_COMMAND
|
|
370
|
+
Services::CreateRepo.new.execute(**options)
|
|
371
|
+
return
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Leave existing commands and repository construction unchanged below this early return.
|
|
376
|
+
|
|
377
|
+
- [ ] **Step 8: Document repository creation**
|
|
378
|
+
|
|
379
|
+
Add repository creation and forking to the supported operations. Add concise samples showing interactive visibility, `--visibility private`, and `--upstream git@github.com:owner/project.git`, plus the effects: SSH `origin`, preserved `upstream`, pushed current branch, and default-branch update.
|
|
380
|
+
|
|
381
|
+
- [ ] **Step 9: Run CLI, type, style, and full test verification**
|
|
382
|
+
|
|
383
|
+
Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb spec/unit/geet_launcher_spec.rb`
|
|
384
|
+
|
|
385
|
+
Expected: all examples pass.
|
|
386
|
+
|
|
387
|
+
Run: `GH_TOKEN=test bundle exec rake`
|
|
388
|
+
|
|
389
|
+
Expected: all examples pass.
|
|
390
|
+
|
|
391
|
+
Run: `bundle exec srb tc`
|
|
392
|
+
|
|
393
|
+
Expected: `No errors! Great job.`
|
|
394
|
+
|
|
395
|
+
Run: `bundle exec rubocop`
|
|
396
|
+
|
|
397
|
+
Expected: no offenses.
|
|
398
|
+
|
|
399
|
+
Run: `git diff --check`
|
|
400
|
+
|
|
401
|
+
Expected: no output and exit status 0.
|
|
402
|
+
|
|
403
|
+
- [ ] **Step 10: Commit CLI integration and documentation**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
git add lib/geet/commandline/commands.rb lib/geet/commandline/configuration.rb bin/geet spec/unit/commandline/configuration_spec.rb spec/unit/geet_launcher_spec.rb README.md
|
|
407
|
+
git commit -m "Expose repository creation command"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Task 5: Final verification
|
|
411
|
+
|
|
412
|
+
**Files:**
|
|
413
|
+
- Verify only; modify files only to correct failures caused by this feature.
|
|
414
|
+
|
|
415
|
+
**Interfaces:**
|
|
416
|
+
- Consumes: all previous tasks.
|
|
417
|
+
- Produces: a clean, verified `repo-create` branch.
|
|
418
|
+
|
|
419
|
+
- [ ] **Step 1: Run the complete verification suite from a clean shell command**
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
GH_TOKEN=test bundle exec rake
|
|
423
|
+
bundle exec srb tc
|
|
424
|
+
bundle exec rubocop
|
|
425
|
+
git diff --check
|
|
426
|
+
git status --short --branch
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Expected: all tests pass, Sorbet reports no errors, RuboCop reports no offenses, diff check is silent, and status shows `## repo-create` with no changed files.
|
|
430
|
+
|
|
431
|
+
- [ ] **Step 2: Review the commit series**
|
|
432
|
+
|
|
433
|
+
Run: `git log --oneline master..HEAD`
|
|
434
|
+
|
|
435
|
+
Expected: the design commit followed by conceptually atomic API, Git primitive, service, and CLI commits.
|