gem-contribute 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +14 -8
- data/.github/workflows/ci.yml +26 -0
- data/.github/workflows/pr-template-check.yml +100 -0
- data/.github/workflows/release.yml +71 -0
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +10 -4
- data/MAINTAINER.md +119 -2
- data/README.md +13 -1
- data/docs/OPEN_QUESTIONS.md +167 -0
- data/docs/ROADMAP.md +266 -0
- data/docs/adr/0006-standalone-gem-not-plugin.md +1 -1
- data/docs/adr/0008-rooibos-tui-framework.md +3 -3
- data/docs/adr/0010-charm-ruby-tui-framework.md +2 -2
- data/docs/adr/0011-host-adapter-owns-host-verbs.md +58 -0
- data/docs/adr/0012-output-free-service-objects-three-interface-architecture.md +79 -0
- data/docs/adr/0013-revert-to-rooibos.md +71 -0
- data/docs/adr/0014-ship-bundler-and-rubygems-plugins.md +75 -0
- data/docs/adr/README.md +7 -3
- data/docs/design-interface-layer.md +295 -0
- data/docs/design.md +31 -8
- data/docs/index.md +1 -1
- data/docs/prep-plan.md +6 -6
- data/lib/gem_contribute/cli/auth.rb +22 -44
- data/lib/gem_contribute/cli/config.rb +29 -15
- data/lib/gem_contribute/cli/fix.rb +122 -0
- data/lib/gem_contribute/cli/fork.rb +145 -0
- data/lib/gem_contribute/cli/init.rb +19 -24
- data/lib/gem_contribute/cli/issue_announcer.rb +42 -0
- data/lib/gem_contribute/cli/issues.rb +36 -47
- data/lib/gem_contribute/cli/platform_tools.rb +33 -0
- data/lib/gem_contribute/cli/post_clone_hooks.rb +50 -0
- data/lib/gem_contribute/cli/rate_limit_footer.rb +5 -3
- data/lib/gem_contribute/cli/scan.rb +20 -16
- data/lib/gem_contribute/cli/submit.rb +60 -64
- data/lib/gem_contribute/cli/workflow.rb +63 -0
- data/lib/gem_contribute/cli.rb +9 -16
- data/lib/gem_contribute/config.rb +27 -1
- data/lib/gem_contribute/git.rb +49 -0
- data/lib/gem_contribute/host_adapter.rb +52 -5
- data/lib/gem_contribute/host_adapters/github_adapter.rb +126 -37
- data/lib/gem_contribute/operations/announce.rb +52 -0
- data/lib/gem_contribute/operations/branch.rb +35 -0
- data/lib/gem_contribute/operations/clone.rb +41 -0
- data/lib/gem_contribute/operations/fix_pipeline.rb +70 -0
- data/lib/gem_contribute/operations/fork.rb +35 -0
- data/lib/gem_contribute/output/null.rb +20 -0
- data/lib/gem_contribute/output/standard.rb +71 -0
- data/lib/gem_contribute/version.rb +1 -1
- data/lib/gem_contribute.rb +10 -18
- metadata +115 -5
- data/lib/gem_contribute/cli/fork_clone_branch.rb +0 -204
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gem-contribute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Hagmann
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-05-04 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: bundler
|
|
@@ -23,10 +24,94 @@ dependencies:
|
|
|
23
24
|
- - ">="
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
26
|
version: '2.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: dry-initializer
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: dry-monads
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.10'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.10'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: dry-operation
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.1'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.1'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: tty-prompt
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.23'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.23'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: tty-spinner
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.9'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.9'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: zeitwerk
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '2.6'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '2.6'
|
|
26
111
|
description: |
|
|
27
112
|
gem-contribute reads a project's Gemfile.lock, resolves each gem's source
|
|
28
113
|
repository via the RubyGems API, surfaces open contributable issues from
|
|
29
|
-
those repositories, and offers a one-keystroke
|
|
114
|
+
those repositories, and offers a one-keystroke fix flow so a
|
|
30
115
|
developer can go from "I noticed an issue" to "I have a working branch" in
|
|
31
116
|
seconds. v0.1 supports GitHub-hosted gems with OAuth device-flow auth.
|
|
32
117
|
email:
|
|
@@ -40,6 +125,9 @@ files:
|
|
|
40
125
|
- ".github/ISSUE_TEMPLATE/workshop-issue.md"
|
|
41
126
|
- ".github/PULL_REQUEST_TEMPLATE.md"
|
|
42
127
|
- ".github/workflows/auto-merge-kicked-tires.yml"
|
|
128
|
+
- ".github/workflows/ci.yml"
|
|
129
|
+
- ".github/workflows/pr-template-check.yml"
|
|
130
|
+
- ".github/workflows/release.yml"
|
|
43
131
|
- CHANGELOG.md
|
|
44
132
|
- CLAUDE.md
|
|
45
133
|
- CODE_OF_CONDUCT.md
|
|
@@ -49,6 +137,8 @@ files:
|
|
|
49
137
|
- MAINTAINER.md
|
|
50
138
|
- README.md
|
|
51
139
|
- Rakefile
|
|
140
|
+
- docs/OPEN_QUESTIONS.md
|
|
141
|
+
- docs/ROADMAP.md
|
|
52
142
|
- docs/_config.yml
|
|
53
143
|
- docs/adr/0001-just-in-time-auth.md
|
|
54
144
|
- docs/adr/0002-bundler-lockfile-parser.md
|
|
@@ -60,8 +150,13 @@ files:
|
|
|
60
150
|
- docs/adr/0008-rooibos-tui-framework.md
|
|
61
151
|
- docs/adr/0009-top-level-namespace.md
|
|
62
152
|
- docs/adr/0010-charm-ruby-tui-framework.md
|
|
153
|
+
- docs/adr/0011-host-adapter-owns-host-verbs.md
|
|
154
|
+
- docs/adr/0012-output-free-service-objects-three-interface-architecture.md
|
|
155
|
+
- docs/adr/0013-revert-to-rooibos.md
|
|
156
|
+
- docs/adr/0014-ship-bundler-and-rubygems-plugins.md
|
|
63
157
|
- docs/adr/README.md
|
|
64
158
|
- docs/claude-code-prompt.md
|
|
159
|
+
- docs/design-interface-layer.md
|
|
65
160
|
- docs/design.md
|
|
66
161
|
- docs/ideas.md
|
|
67
162
|
- docs/index.md
|
|
@@ -78,18 +173,31 @@ files:
|
|
|
78
173
|
- lib/gem_contribute/cli.rb
|
|
79
174
|
- lib/gem_contribute/cli/auth.rb
|
|
80
175
|
- lib/gem_contribute/cli/config.rb
|
|
81
|
-
- lib/gem_contribute/cli/
|
|
176
|
+
- lib/gem_contribute/cli/fix.rb
|
|
177
|
+
- lib/gem_contribute/cli/fork.rb
|
|
82
178
|
- lib/gem_contribute/cli/init.rb
|
|
179
|
+
- lib/gem_contribute/cli/issue_announcer.rb
|
|
83
180
|
- lib/gem_contribute/cli/issues.rb
|
|
181
|
+
- lib/gem_contribute/cli/platform_tools.rb
|
|
182
|
+
- lib/gem_contribute/cli/post_clone_hooks.rb
|
|
84
183
|
- lib/gem_contribute/cli/rate_limit_footer.rb
|
|
85
184
|
- lib/gem_contribute/cli/scan.rb
|
|
86
185
|
- lib/gem_contribute/cli/submit.rb
|
|
186
|
+
- lib/gem_contribute/cli/workflow.rb
|
|
87
187
|
- lib/gem_contribute/config.rb
|
|
88
188
|
- lib/gem_contribute/errors.rb
|
|
189
|
+
- lib/gem_contribute/git.rb
|
|
89
190
|
- lib/gem_contribute/host_adapter.rb
|
|
90
191
|
- lib/gem_contribute/host_adapters/github_adapter.rb
|
|
91
192
|
- lib/gem_contribute/locked_gem.rb
|
|
92
193
|
- lib/gem_contribute/lockfile_parser.rb
|
|
194
|
+
- lib/gem_contribute/operations/announce.rb
|
|
195
|
+
- lib/gem_contribute/operations/branch.rb
|
|
196
|
+
- lib/gem_contribute/operations/clone.rb
|
|
197
|
+
- lib/gem_contribute/operations/fix_pipeline.rb
|
|
198
|
+
- lib/gem_contribute/operations/fork.rb
|
|
199
|
+
- lib/gem_contribute/output/null.rb
|
|
200
|
+
- lib/gem_contribute/output/standard.rb
|
|
93
201
|
- lib/gem_contribute/project.rb
|
|
94
202
|
- lib/gem_contribute/resolver.rb
|
|
95
203
|
- lib/gem_contribute/token_store.rb
|
|
@@ -105,6 +213,7 @@ metadata:
|
|
|
105
213
|
changelog_uri: https://github.com/cdhagmann/gem-contribute/blob/main/CHANGELOG.md
|
|
106
214
|
documentation_uri: https://cdhagmann.com/gem-contribute/
|
|
107
215
|
rubygems_mfa_required: 'true'
|
|
216
|
+
post_install_message:
|
|
108
217
|
rdoc_options: []
|
|
109
218
|
require_paths:
|
|
110
219
|
- lib
|
|
@@ -119,7 +228,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
228
|
- !ruby/object:Gem::Version
|
|
120
229
|
version: '0'
|
|
121
230
|
requirements: []
|
|
122
|
-
rubygems_version: 4.
|
|
231
|
+
rubygems_version: 3.4.19
|
|
232
|
+
signing_key:
|
|
123
233
|
specification_version: 4
|
|
124
234
|
summary: Find and contribute to the open-source Ruby gems your project depends on.
|
|
125
235
|
test_files: []
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "open3"
|
|
4
|
-
|
|
5
|
-
module GemContribute
|
|
6
|
-
module CLI
|
|
7
|
-
# `gem-contribute fork-clone-branch <gem>/<issue#>`
|
|
8
|
-
#
|
|
9
|
-
# Performs the full sequence the TUI's `f` keybinding will trigger in
|
|
10
|
-
# Stage 3:
|
|
11
|
-
#
|
|
12
|
-
# 1. Resolve <gem> via the RubyGems Resolver (no lockfile required;
|
|
13
|
-
# the lockfile is for discovery via `scan`, not gating here).
|
|
14
|
-
# 2. Read the cached GitHub token; raise AuthRequired with a clear
|
|
15
|
-
# `auth login` hint if missing.
|
|
16
|
-
# 3. Look up the viewer's login.
|
|
17
|
-
# 4. If they don't already have a fork, fork the upstream repo.
|
|
18
|
-
# 5. Poll until the fork is reachable (forks return 202 immediately
|
|
19
|
-
# but the resource may 404 for a few seconds).
|
|
20
|
-
# 6. `git clone` the fork to `<clone_root>/<owner>/<repo>`.
|
|
21
|
-
# 7. `git checkout -b gem-contribute/issue-<N>` from the default branch.
|
|
22
|
-
# 8. Print the local path on stdout.
|
|
23
|
-
#
|
|
24
|
-
# The shell-outs use Open3 with explicit args (not strings) to avoid any
|
|
25
|
-
# shell-injection surface.
|
|
26
|
-
class ForkCloneBranch
|
|
27
|
-
DEFAULT_CLONE_ROOT = File.expand_path("~/code/oss")
|
|
28
|
-
BRANCH_PREFIX = "gem-contribute/issue-"
|
|
29
|
-
FORK_READINESS_RETRIES = 12 # 12 × 5s = 60s ceiling
|
|
30
|
-
FORK_READINESS_INTERVAL = 5
|
|
31
|
-
|
|
32
|
-
def initialize(stdout: $stdout,
|
|
33
|
-
stderr: $stderr,
|
|
34
|
-
resolver: Resolver.new,
|
|
35
|
-
store: TokenStore.new,
|
|
36
|
-
adapter_factory: ->(token:) { HostAdapters::GitHubAdapter.new(token: token) },
|
|
37
|
-
git: Git.new,
|
|
38
|
-
clone_root: DEFAULT_CLONE_ROOT,
|
|
39
|
-
sleeper: ->(s) { Kernel.sleep(s) })
|
|
40
|
-
@stdout = stdout
|
|
41
|
-
@stderr = stderr
|
|
42
|
-
@resolver = resolver
|
|
43
|
-
@store = store
|
|
44
|
-
@adapter_factory = adapter_factory
|
|
45
|
-
@git = git
|
|
46
|
-
@clone_root = clone_root
|
|
47
|
-
@sleeper = sleeper
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def run(argv)
|
|
51
|
-
return missing_clone_root if @clone_root.nil?
|
|
52
|
-
|
|
53
|
-
target = argv.shift
|
|
54
|
-
return print_usage_error if target.nil? || !target.include?("/")
|
|
55
|
-
|
|
56
|
-
gem_name, issue = target.split("/", 2)
|
|
57
|
-
adapter = build_adapter
|
|
58
|
-
return 1 if adapter.nil?
|
|
59
|
-
|
|
60
|
-
project = resolve_or_fail(gem_name)
|
|
61
|
-
return 1 if project.nil?
|
|
62
|
-
|
|
63
|
-
execute(adapter, project, issue)
|
|
64
|
-
rescue AuthRequired
|
|
65
|
-
@stderr.puts "Not authenticated. Run `gem-contribute auth login` first."
|
|
66
|
-
1
|
|
67
|
-
rescue AdapterError => e
|
|
68
|
-
@stderr.puts "fork-clone-branch failed: #{e.message}"
|
|
69
|
-
1
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
def missing_clone_root
|
|
75
|
-
@stderr.puts "clone_root is not configured. Run `gem-contribute init` first."
|
|
76
|
-
1
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def print_usage_error
|
|
80
|
-
@stderr.puts "Usage: gem-contribute fork-clone-branch <gem>/<issue#>"
|
|
81
|
-
2
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def build_adapter
|
|
85
|
-
token = @store.token_for("github.com")
|
|
86
|
-
if token.nil?
|
|
87
|
-
@stderr.puts "Not authenticated. Run `gem-contribute auth login` first."
|
|
88
|
-
return nil
|
|
89
|
-
end
|
|
90
|
-
@adapter_factory.call(token: token)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def resolve_or_fail(gem_name)
|
|
94
|
-
return GemContribute::SELF_PROJECT if gem_name == GemContribute::SELF_PROJECT.gem_name
|
|
95
|
-
|
|
96
|
-
gem = LockedGem.new(name: gem_name, version: "*", source_type: :rubygems, source_uri: "https://rubygems.org/")
|
|
97
|
-
project = @resolver.resolve(gem)
|
|
98
|
-
|
|
99
|
-
if project.host != "github.com"
|
|
100
|
-
@stderr.puts "Cannot fork-clone-branch: #{gem_name} resolves to #{project.host} " \
|
|
101
|
-
"(only github.com is supported at v0.1)"
|
|
102
|
-
return nil
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
project
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def execute(adapter, project, issue)
|
|
109
|
-
viewer = adapter.viewer_login
|
|
110
|
-
clone_url = ensure_fork(adapter, project, viewer)
|
|
111
|
-
local_path = clone_into_root(project, clone_url)
|
|
112
|
-
branch_name = "#{BRANCH_PREFIX}#{issue}"
|
|
113
|
-
@git.checkout_branch(local_path, branch_name)
|
|
114
|
-
# `submit` needs to know the canonical project to point the PR at.
|
|
115
|
-
# Naming it `upstream` follows the standard fork workflow convention.
|
|
116
|
-
@git.add_remote(local_path, "upstream",
|
|
117
|
-
"https://github.com/#{project.owner}/#{project.repo}.git")
|
|
118
|
-
|
|
119
|
-
@stdout.puts "Forked, cloned, and branched."
|
|
120
|
-
@stdout.puts " path: #{local_path}"
|
|
121
|
-
@stdout.puts " branch: #{branch_name}"
|
|
122
|
-
@stdout.puts " upstream: https://github.com/#{project.owner}/#{project.repo}"
|
|
123
|
-
@stdout.puts " fork: https://github.com/#{viewer}/#{project.repo}"
|
|
124
|
-
@stdout.puts
|
|
125
|
-
@stdout.puts "Next: cd #{local_path} && make your changes, then `gem-contribute submit`."
|
|
126
|
-
0
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def ensure_fork(adapter, project, viewer)
|
|
130
|
-
if adapter.already_forked?(project)
|
|
131
|
-
@stdout.puts "You already have a fork at #{viewer}/#{project.repo}. Skipping fork."
|
|
132
|
-
return "https://github.com/#{viewer}/#{project.repo}.git"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
@stdout.puts "Forking #{project.owner}/#{project.repo} → #{viewer}/#{project.repo}..."
|
|
136
|
-
body = adapter.fork(project)
|
|
137
|
-
wait_until_ready(adapter, viewer, project.repo)
|
|
138
|
-
body.fetch("clone_url")
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def wait_until_ready(adapter, viewer, name)
|
|
142
|
-
ready = FORK_READINESS_RETRIES.times.any? do |i|
|
|
143
|
-
break true if adapter.fork_ready?(viewer, name)
|
|
144
|
-
|
|
145
|
-
@sleeper.call(FORK_READINESS_INTERVAL) unless i == FORK_READINESS_RETRIES - 1
|
|
146
|
-
false
|
|
147
|
-
end
|
|
148
|
-
return if ready
|
|
149
|
-
|
|
150
|
-
raise AdapterError, "fork not reachable after #{FORK_READINESS_RETRIES * FORK_READINESS_INTERVAL}s"
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def clone_into_root(project, clone_url)
|
|
154
|
-
target = File.join(@clone_root, project.owner, project.repo)
|
|
155
|
-
if File.directory?(File.join(target, ".git"))
|
|
156
|
-
@stdout.puts "Reusing existing clone at #{target}."
|
|
157
|
-
return target
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
FileUtils.mkdir_p(File.dirname(target))
|
|
161
|
-
@stdout.puts "Cloning into #{target}..."
|
|
162
|
-
@git.clone(clone_url, target)
|
|
163
|
-
target
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Thin wrapper around git so the spec can swap in a fake without shelling
|
|
168
|
-
# out. The real implementation uses Open3 with arg-list invocation — no
|
|
169
|
-
# shell, so no injection surface.
|
|
170
|
-
class Git
|
|
171
|
-
def clone(url, target)
|
|
172
|
-
run!(["git", "clone", url, target])
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def checkout_branch(path, branch)
|
|
176
|
-
run!(["git", "-C", path, "checkout", "-b", branch])
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def add_remote(path, name, url)
|
|
180
|
-
# Idempotent: if the remote already exists (e.g. reusing a clone)
|
|
181
|
-
# we silently succeed rather than fail the whole flow.
|
|
182
|
-
return if remote_exists?(path, name)
|
|
183
|
-
|
|
184
|
-
run!(["git", "-C", path, "remote", "add", name, url])
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def push(path, remote, branch)
|
|
188
|
-
run!(["git", "-C", path, "push", "-u", remote, branch])
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def remote_exists?(path, name)
|
|
192
|
-
out, _err, status = Open3.capture3("git", "-C", path, "remote")
|
|
193
|
-
status.success? && out.split("\n").include?(name)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def run!(argv)
|
|
197
|
-
_stdout, stderr_str, status = Open3.capture3(*argv)
|
|
198
|
-
return if status.success?
|
|
199
|
-
|
|
200
|
-
raise GemContribute::AdapterError, "git #{argv[1..].join(" ")} failed: #{stderr_str.strip}"
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|