ace-git-forgejo 0.7.0 → 0.9.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 +17 -0
- data/lib/ace/git/forgejo/issue_api.rb +13 -3
- data/lib/ace/git/forgejo/provider.rb +73 -0
- data/lib/ace/git/forgejo/pull_request_api.rb +12 -0
- data/lib/ace/git/forgejo/version.rb +1 -1
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f9063026140a927fbd346c52d1bcad410b23dc0fbaa8e8d69eae206688afd72
|
|
4
|
+
data.tar.gz: d2731b929dc9c5adde5d1a21259bc071ad54771424be3d5f3a7e2465891a3eb7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b69e571f2eb13eff2a71f082d8630a5610fc4827c7ea2fe6e126cef0a04610257e9bc494053ca615b8fccf88ba49b956e51dbf16768e9c2dc990a55c6d9fde7d
|
|
7
|
+
data.tar.gz: 2bda0f62a97a58ef95c67495dabb2085401be57d3002f5cfb97fd5f7a6a92b173f8aebc5c5cde5519ca0f64cc03c49de307df3021f70a7fa6988e4a675589377
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Observe repository-bound open issues including body and label changes. Support formal approval with repository-scoped credentials and verify the returned reviewer identity.
|
|
15
|
+
|
|
16
|
+
## [0.8.0] - 2026-10-23
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Formal reviewer-owned, exact-head provider approval and read-only approval verification through the forge-neutral review flow; comments do not substitute for approval.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Publish formal approval directly at explicit commit_id instead of rebinding pending-submit; retain safe comment-free owned pending recovery, refuse unrelated pending, ignore request/team rows, and require the verified formal-review API floor.
|
|
26
|
+
|
|
10
27
|
## [0.7.0] - 2026-10-16
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -25,6 +25,10 @@ module Ace
|
|
|
25
25
|
request(:get, "issues/#{number}")
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def open_issues
|
|
29
|
+
paginate("issues?state=open", fail_on_repeated_page: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def comments(number)
|
|
29
33
|
paginate("issues/#{number}/comments")
|
|
30
34
|
end
|
|
@@ -78,22 +82,28 @@ module Ace
|
|
|
78
82
|
|
|
79
83
|
private
|
|
80
84
|
|
|
81
|
-
def paginate(suffix)
|
|
85
|
+
def paginate(suffix, fail_on_repeated_page: false)
|
|
82
86
|
page = 1
|
|
83
87
|
entries = []
|
|
84
88
|
seen = []
|
|
85
89
|
loop do
|
|
86
|
-
|
|
90
|
+
separator = suffix.include?("?") ? "&" : "?"
|
|
91
|
+
batch = request(:get, "#{suffix}#{separator}page=#{page}&limit=50")
|
|
87
92
|
unless batch.is_a?(Array)
|
|
88
93
|
raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} did not return a list"
|
|
89
94
|
end
|
|
90
|
-
|
|
95
|
+
unless batch.all? { |entry| entry.is_a?(Hash) && entry["id"].is_a?(Integer) && entry["id"].positive? }
|
|
96
|
+
raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} contains an invalid item identity"
|
|
97
|
+
end
|
|
91
98
|
# Stop when a page adds no new items (unique ids): instances
|
|
92
99
|
# may cap pages below the requested limit, so a short page alone
|
|
93
100
|
# does not prove the end of the collection.
|
|
94
101
|
fresh = batch.reject { |item| seen.include?(item["id"]) }
|
|
95
102
|
entries.concat(fresh)
|
|
96
103
|
fresh.each { |item| seen << item["id"] }
|
|
104
|
+
if fail_on_repeated_page && !batch.empty? && fresh.empty?
|
|
105
|
+
raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} repeated a page; completeness cannot be proven"
|
|
106
|
+
end
|
|
97
107
|
break if fresh.empty?
|
|
98
108
|
|
|
99
109
|
page += 1
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "ace/git/providers/formal_approval"
|
|
4
5
|
require "uri"
|
|
5
6
|
require "ace/git/atoms/server_url"
|
|
6
7
|
require_relative "cli_executor"
|
|
@@ -118,6 +119,39 @@ module Ace
|
|
|
118
119
|
.first(limit)
|
|
119
120
|
end
|
|
120
121
|
|
|
122
|
+
# Open issue intake uses the selected Forgejo repository, and never
|
|
123
|
+
# substitutes pull requests or an unavailable listing for an empty one.
|
|
124
|
+
# @return [Array<ProviderIssue>] open issues, newest first
|
|
125
|
+
# @raise [Ace::Git::ProviderMalformedOutputError] for invalid evidence
|
|
126
|
+
# @raise [Ace::Git::ProviderIdentityMismatchError] for foreign evidence
|
|
127
|
+
def recent_issues(limit:)
|
|
128
|
+
limit = Integer(limit)
|
|
129
|
+
return [] if limit <= 0
|
|
130
|
+
|
|
131
|
+
issue_api.open_issues.filter_map do |entry|
|
|
132
|
+
next if entry["pull_request"]
|
|
133
|
+
|
|
134
|
+
number = entry["number"]
|
|
135
|
+
unless number.is_a?(Integer) && number.positive? && entry["title"].is_a?(String) &&
|
|
136
|
+
!entry["title"].strip.empty? && entry["state"] == "open"
|
|
137
|
+
raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains an invalid issue"
|
|
138
|
+
end
|
|
139
|
+
unless entry["html_url"] == issue_url(number)
|
|
140
|
+
raise Ace::Git::ProviderIdentityMismatchError, "Forgejo issue listing contains foreign issue ##{number}"
|
|
141
|
+
end
|
|
142
|
+
unless entry["user"].nil? || entry["user"].is_a?(Hash)
|
|
143
|
+
raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains an invalid author"
|
|
144
|
+
end
|
|
145
|
+
unless (entry["body"].nil? || entry["body"].is_a?(String)) &&
|
|
146
|
+
(entry["labels"].nil? || entry["labels"].is_a?(Array) && entry["labels"].all? { |label| label.is_a?(Hash) && label["name"].is_a?(String) })
|
|
147
|
+
raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains invalid body or labels"
|
|
148
|
+
end
|
|
149
|
+
Ace::Git::ProviderIssue.new(server_name: server.name, number: number, title: entry["title"],
|
|
150
|
+
state: :open, author: entry.dig("user", "login"), url: entry["html_url"],
|
|
151
|
+
body: entry["body"], labels: Array(entry["labels"]).map { |label| label.fetch("name") })
|
|
152
|
+
end.sort_by { |issue| -issue.number }.first(limit)
|
|
153
|
+
end
|
|
154
|
+
|
|
121
155
|
# @return [ProviderIssue] normalized issue evidence
|
|
122
156
|
def issue(number:)
|
|
123
157
|
view = fj(:issue_view, request_number!(number))
|
|
@@ -340,6 +374,14 @@ module Ace
|
|
|
340
374
|
raise Ace::Git::ProviderMalformedOutputError, "Malformed Forgejo repository file content: #{e.message}"
|
|
341
375
|
end
|
|
342
376
|
|
|
377
|
+
def approve_pull_request(number:, expected_head:, body:, correlation:)
|
|
378
|
+
formal_approval.approve(number: number, expected_head: expected_head, body: body, correlation: correlation)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def pull_request_approval(number:, expected_head:, review_id:)
|
|
382
|
+
formal_approval.read(number: number, expected_head: expected_head, review_id: review_id)
|
|
383
|
+
end
|
|
384
|
+
|
|
343
385
|
def create_pull_request_comment(number:, expected_head:, body:, correlation:)
|
|
344
386
|
number = request_number!(number)
|
|
345
387
|
pr = verify_expected_head!(pull_request(number: number), expected_head)
|
|
@@ -914,6 +956,37 @@ module Ace
|
|
|
914
956
|
"Cannot post review comment to PR ##{pr.number} in #{pr.state} state"
|
|
915
957
|
end
|
|
916
958
|
|
|
959
|
+
def formal_approval
|
|
960
|
+
pr_api.ensure_formal_review_supported!
|
|
961
|
+
request = lambda do |method, path, payload|
|
|
962
|
+
path = "/api/v1/user" if path == :actor
|
|
963
|
+
payload = payload.merge("event" => "APPROVED") if method == :post
|
|
964
|
+
review_http.request(method, path, body: payload)
|
|
965
|
+
end
|
|
966
|
+
Ace::Git::Providers::FormalApproval.new(provider: self, request: request,
|
|
967
|
+
list: method(:formal_review_rows), official: true, pending_reuse: true,
|
|
968
|
+
expected_reviewer: leased_reviewer_account)
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
# Repository-scoped leases cannot call /api/v1/user. The issuer's
|
|
972
|
+
# account is an expectation; the forge review supplies the identity.
|
|
973
|
+
def leased_reviewer_account
|
|
974
|
+
return unless ENV["ACE_LAB_FORGEJO_ORIGIN"] == RepositoryBinding::Target.resolve(server.url).host_url
|
|
975
|
+
|
|
976
|
+
account = ENV["ACE_LAB_FORGEJO_ACCOUNT"]
|
|
977
|
+
return account if account&.match?(/\A[a-zA-Z0-9_.-]+\z/)
|
|
978
|
+
|
|
979
|
+
raise Ace::Git::ProviderAuthenticationError, "Forgejo lease has no valid reviewer account"
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
# Forgejo lists review requests alongside actual reviews. User and
|
|
983
|
+
# team requests have no reviewed commit; they are not approval evidence.
|
|
984
|
+
def formal_review_rows(path)
|
|
985
|
+
review_http.paginate(path).reject do |row|
|
|
986
|
+
row.is_a?(Hash) && row["state"] == "REQUEST_REVIEW"
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
|
|
917
990
|
def review_http
|
|
918
991
|
@review_http ||= HttpClient.new(server: server, timeout: timeout, runner: runner)
|
|
919
992
|
end
|
|
@@ -70,9 +70,21 @@ module Ace
|
|
|
70
70
|
"Forgejo server reports #{version}; PR delivery requires #{MINIMUM_SERVER_VERSION} " \
|
|
71
71
|
"(server-enforced expected-head merge is not documented below it)"
|
|
72
72
|
end
|
|
73
|
+
@server_version = version
|
|
73
74
|
@version_supported = true
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# Formal create-with-event preserves explicit commit_id in the
|
|
78
|
+
# inspected Forgejo v11.0.0 and v16.0.5 router/service/model sources.
|
|
79
|
+
# Pending submission does not: it rebinds to current HEAD and is unused.
|
|
80
|
+
def ensure_formal_review_supported!
|
|
81
|
+
ensure_version_supported!
|
|
82
|
+
unless @server_version >= Gem::Version.new("11.0.0")
|
|
83
|
+
raise Ace::Git::ProviderUnsupportedCapabilityError,
|
|
84
|
+
"Formal exact-commit approval requires Forgejo 11.0.0 or newer"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
76
88
|
# @return [Hash] the raw validated pull request payload
|
|
77
89
|
# @raise [Ace::Git::ProviderObjectNotFoundError] when absent
|
|
78
90
|
# @raise [Ace::Git::ProviderMalformedOutputError] on schema violation
|
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.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10
|
|
10
|
+
date: 2026-11-10 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-git
|
|
@@ -15,14 +15,20 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.31'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.33.0
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - "~>"
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
28
|
+
version: '0.31'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.33.0
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: faraday
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|