ace-git-forgejo 0.2.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 +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +35 -2
- data/lib/ace/git/forgejo/cli_executor.rb +112 -31
- data/lib/ace/git/forgejo/http_client.rb +111 -0
- data/lib/ace/git/forgejo/parsers.rb +23 -4
- data/lib/ace/git/forgejo/provider.rb +451 -56
- data/lib/ace/git/forgejo/repository_binding.rb +206 -0
- data/lib/ace/git/forgejo/version.rb +1 -1
- data/lib/ace/git/forgejo.rb +2 -0
- metadata +6 -4
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Git
|
|
8
|
+
module Forgejo
|
|
9
|
+
# Selected-server identity binding for `fj` repository commands.
|
|
10
|
+
#
|
|
11
|
+
# Forgejo CLI v0.6.0 exposes two explicit-target mechanisms and no
|
|
12
|
+
# others (see the task evidence file): `-H HOST` plus either
|
|
13
|
+
# `-r OWNER/REPO` (search, create, issue/actions repo selection) or a
|
|
14
|
+
# qualified `OWNER/REPO#N` object id (view/edit/status family). A
|
|
15
|
+
# repository command may only launch through {RepositoryBinding.argv_for},
|
|
16
|
+
# which exists for operations whose argv form was observed on a real
|
|
17
|
+
# binary; everything else refuses before any subprocess.
|
|
18
|
+
module RepositoryBinding
|
|
19
|
+
# forgejo-cli versions whose help/output this table was observed on.
|
|
20
|
+
# A different installed version must not inherit these capabilities;
|
|
21
|
+
# the executor refuses it until the table is re-observed and extended.
|
|
22
|
+
OBSERVED_VERSIONS = ["v0.6.0"].freeze
|
|
23
|
+
|
|
24
|
+
# The validated selected repository. Built once from the resolved
|
|
25
|
+
# server URL; malformed selections fail with ConfigError before any
|
|
26
|
+
# subprocess launch.
|
|
27
|
+
class Target
|
|
28
|
+
attr_reader :host, :authority, :host_url, :repo, :url
|
|
29
|
+
|
|
30
|
+
SUPPORTED_SCHEMES = ["http", "https"].freeze
|
|
31
|
+
|
|
32
|
+
# @param server_url [String] resolved server URL (https://host/owner/repo)
|
|
33
|
+
# @raise [Ace::Git::ConfigError] when host, scheme, or owner/repository is missing
|
|
34
|
+
def self.resolve(server_url)
|
|
35
|
+
uri = URI.parse(server_url.to_s)
|
|
36
|
+
raise_config_error!(server_url, "missing host") if uri.host.nil? || uri.host.empty?
|
|
37
|
+
unless SUPPORTED_SCHEMES.include?(uri.scheme.to_s.downcase)
|
|
38
|
+
raise_config_error!(server_url, "scheme must be http or https")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
authority = if uri.port && uri.port != uri.default_port
|
|
42
|
+
"#{uri.host}:#{uri.port}"
|
|
43
|
+
else
|
|
44
|
+
uri.host
|
|
45
|
+
end
|
|
46
|
+
path = uri.path.to_s.sub(%r{/+\z}, "").sub(/\.git\z/i, "")
|
|
47
|
+
segments = path.split("/").reject(&:empty?)
|
|
48
|
+
unless segments.length == 2
|
|
49
|
+
raise_config_error!(server_url, "path must be exactly owner/repository")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
repo = "#{segments[0]}/#{segments[1]}"
|
|
53
|
+
# `fj -H` accepts a full URL and otherwise assumes HTTPS (observed
|
|
54
|
+
# v0.6.0); passing scheme + authority preserves the configured
|
|
55
|
+
# endpoint exactly.
|
|
56
|
+
host_url = "#{uri.scheme.downcase}://#{authority}"
|
|
57
|
+
new(
|
|
58
|
+
host: uri.host, authority: authority, host_url: host_url,
|
|
59
|
+
repo: repo, url: "#{host_url}/#{repo}"
|
|
60
|
+
)
|
|
61
|
+
rescue URI::Error
|
|
62
|
+
raise_config_error!(server_url, "unparseable URL")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.raise_config_error!(server_url, reason)
|
|
66
|
+
raise Ace::Git::ConfigError,
|
|
67
|
+
"Selected Forgejo server URL #{server_url.inspect} is unusable (#{reason}); " \
|
|
68
|
+
"configure the server as scheme://host/owner/repository"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(host:, authority:, host_url:, repo:, url:)
|
|
72
|
+
@host = host
|
|
73
|
+
@authority = authority
|
|
74
|
+
@host_url = host_url
|
|
75
|
+
@repo = repo
|
|
76
|
+
@url = url
|
|
77
|
+
end
|
|
78
|
+
private_class_method :new
|
|
79
|
+
|
|
80
|
+
# Qualified object id form accepted by `fj pr/issue view|edit` (v0.6.0).
|
|
81
|
+
def qualified_ref(number)
|
|
82
|
+
"#{repo}##{number}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Capability table: repository operations with an argv form observed
|
|
87
|
+
# on real forgejo-cli v0.6.0 (task evidence file). Forms receive the
|
|
88
|
+
# validated Target plus the provider's per-call arguments and return
|
|
89
|
+
# the argv after the binary name; the executor stamps `-H host`.
|
|
90
|
+
FORMS = {
|
|
91
|
+
pr_view: ->(target, number) do
|
|
92
|
+
["--style", "minimal", "pr", "view", target.qualified_ref(number)]
|
|
93
|
+
end,
|
|
94
|
+
pr_commits: ->(target, number) do
|
|
95
|
+
["--style", "minimal", "pr", "view", target.qualified_ref(number), "commits"]
|
|
96
|
+
end,
|
|
97
|
+
pr_diff: ->(target, number) do
|
|
98
|
+
["pr", "view", target.qualified_ref(number), "diff"]
|
|
99
|
+
end,
|
|
100
|
+
pr_search: ->(target, state) do
|
|
101
|
+
["--style", "minimal", "pr", "search", "--state", state, "-r", target.repo]
|
|
102
|
+
end,
|
|
103
|
+
pr_create: ->(target, title, head_ref, base_ref, body) do
|
|
104
|
+
argv = ["pr", "create", title, "--head", head_ref, "--base", base_ref]
|
|
105
|
+
argv += ["--body", body] if body
|
|
106
|
+
argv + ["-r", target.repo]
|
|
107
|
+
end,
|
|
108
|
+
pr_edit_title: ->(target, number, title) do
|
|
109
|
+
["pr", "edit", target.qualified_ref(number), "title", title]
|
|
110
|
+
end,
|
|
111
|
+
pr_edit_body: ->(target, number, body) do
|
|
112
|
+
["pr", "edit", target.qualified_ref(number), "body", body]
|
|
113
|
+
end,
|
|
114
|
+
issue_view: ->(target, number) do
|
|
115
|
+
["--style", "minimal", "issue", "view", target.qualified_ref(number)]
|
|
116
|
+
end,
|
|
117
|
+
actions_tasks: ->(target) do
|
|
118
|
+
["--style", "minimal", "actions", "tasks", "-r", target.repo]
|
|
119
|
+
end,
|
|
120
|
+
repo_view: ->(target) do
|
|
121
|
+
["--style", "minimal", "repo", "view", target.repo]
|
|
122
|
+
end
|
|
123
|
+
}.freeze
|
|
124
|
+
|
|
125
|
+
class << self
|
|
126
|
+
# Resolve one repository operation to its explicitly bound argv.
|
|
127
|
+
#
|
|
128
|
+
# @param operation [Symbol] key in {FORMS}
|
|
129
|
+
# @param target [Target] validated selected repository
|
|
130
|
+
# @param arguments [Array] per-operation arguments
|
|
131
|
+
# @raise [Ace::Git::ProviderUnsupportedCapabilityError] when the
|
|
132
|
+
# operation has no observed form
|
|
133
|
+
def argv_for(operation, target, arguments)
|
|
134
|
+
form = FORMS.fetch(operation) do
|
|
135
|
+
raise Ace::Git::ProviderUnsupportedCapabilityError,
|
|
136
|
+
"forgejo-cli #{OBSERVED_VERSIONS.join("/")} exposes no repository-bound " \
|
|
137
|
+
"argv form for #{operation.inspect}; refusing to run it against #{target.repo}"
|
|
138
|
+
end
|
|
139
|
+
form.call(target, *arguments)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Refuse to launch repository commands when the readable fj keys
|
|
143
|
+
# file contains an alias that redirects the selected host. fj
|
|
144
|
+
# v0.6.0 applies its `aliases` map to `-H` values (observed: an
|
|
145
|
+
# alias silently rewrites the endpoint), and no CLI command prints
|
|
146
|
+
# aliases, so ACE verifies the one configuration file it must
|
|
147
|
+
# agree with — read-only, never modified. An absent or unreadable
|
|
148
|
+
# keys file (other fj versions, moved storage) cannot be verified
|
|
149
|
+
# and is allowed; misdirected runtime requests still classify
|
|
150
|
+
# through the shared taxonomy.
|
|
151
|
+
#
|
|
152
|
+
# @param host_url [String] selected scheme://authority
|
|
153
|
+
# @param config_path [String, nil] explicit keys-file path (tests)
|
|
154
|
+
# @raise [Ace::Git::ConfigError] on a conflicting redirect
|
|
155
|
+
def reject_selected_host_redirect!(host_url, config_path: nil)
|
|
156
|
+
path = config_path || default_keys_path
|
|
157
|
+
return unless path && File.exist?(path)
|
|
158
|
+
|
|
159
|
+
data = JSON.parse(File.read(path))
|
|
160
|
+
aliases = data.is_a?(Hash) ? data["aliases"] : nil
|
|
161
|
+
return unless aliases.is_a?(Hash)
|
|
162
|
+
|
|
163
|
+
selected = strip_scheme(host_url)
|
|
164
|
+
redirect = aliases[selected] || aliases[host_url.to_s]
|
|
165
|
+
return if redirect.nil? || redirect.to_s.empty?
|
|
166
|
+
|
|
167
|
+
redirect_authority = strip_scheme(redirect)
|
|
168
|
+
return if redirect_authority.casecmp(selected).zero?
|
|
169
|
+
|
|
170
|
+
raise Ace::Git::ConfigError,
|
|
171
|
+
"fj configuration redirects selected host #{selected} to #{redirect} " \
|
|
172
|
+
"(#{path}); remove or fix the conflicting fj alias — ACE will not " \
|
|
173
|
+
"operate on a redirected endpoint"
|
|
174
|
+
rescue JSON::ParserError, IOError, SystemCallError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Observed forgejo-cli v0.6.0 keys-file locations (data_dir of the
|
|
179
|
+
# `directories` crate, plus the legacy Cyborus org path).
|
|
180
|
+
def default_keys_path
|
|
181
|
+
candidates = []
|
|
182
|
+
xdg = ENV["XDG_DATA_HOME"]
|
|
183
|
+
candidates << File.join(xdg, "forgejo-cli", "keys.json") if xdg && !xdg.empty?
|
|
184
|
+
home = Dir.home rescue nil
|
|
185
|
+
if home
|
|
186
|
+
candidates << File.join(home, ".local", "share", "forgejo-cli", "keys.json")
|
|
187
|
+
candidates << File.join(home, "Library", "Application Support", "forgejo-cli", "keys.json")
|
|
188
|
+
# macOS app-support uses reverse-DNS bundle directories.
|
|
189
|
+
candidates << File.join(home, "Library", "Application Support", "forgejo-cli.forgejo-cli", "keys.json")
|
|
190
|
+
candidates << File.join(home, ".local", "share", "Cyborus", "forgejo-cli", "keys.json")
|
|
191
|
+
candidates << File.join(home, "Library", "Application Support", "Cyborus.forgejo-cli", "keys.json")
|
|
192
|
+
candidates << File.join(home, "Library", "Application Support", "Cyborus", "forgejo-cli", "keys.json")
|
|
193
|
+
end
|
|
194
|
+
candidates.find { |path| File.exist?(path) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def strip_scheme(url)
|
|
200
|
+
url.to_s.sub(%r{\A[a-z][a-z0-9+.\-]*://}i, "").chomp("/")
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
data/lib/ace/git/forgejo.rb
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
require "ace/git"
|
|
4
4
|
|
|
5
5
|
require_relative "forgejo/version"
|
|
6
|
+
require_relative "forgejo/repository_binding"
|
|
6
7
|
require_relative "forgejo/cli_executor"
|
|
7
8
|
require_relative "forgejo/pr_identifier"
|
|
8
9
|
require_relative "forgejo/parsers"
|
|
10
|
+
require_relative "forgejo/http_client"
|
|
9
11
|
require_relative "forgejo/provider"
|
|
10
12
|
|
|
11
13
|
module Ace
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-git-forgejo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-git
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.26'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.26'
|
|
26
26
|
description: 'Implements the shared ace-git provider contract for Forgejo: all `fj`
|
|
27
27
|
CLI invocation, output parsing, authentication verification, and classified failure
|
|
28
28
|
handling live behind one normalized evidence boundary. Ad-hoc curl fallbacks are
|
|
@@ -39,9 +39,11 @@ files:
|
|
|
39
39
|
- Rakefile
|
|
40
40
|
- lib/ace/git/forgejo.rb
|
|
41
41
|
- lib/ace/git/forgejo/cli_executor.rb
|
|
42
|
+
- lib/ace/git/forgejo/http_client.rb
|
|
42
43
|
- lib/ace/git/forgejo/parsers.rb
|
|
43
44
|
- lib/ace/git/forgejo/pr_identifier.rb
|
|
44
45
|
- lib/ace/git/forgejo/provider.rb
|
|
46
|
+
- lib/ace/git/forgejo/repository_binding.rb
|
|
45
47
|
- lib/ace/git/forgejo/version.rb
|
|
46
48
|
homepage: https://forgejo.tail6c0887.ts.net/cs3b/ace
|
|
47
49
|
licenses:
|