git-bot-feedback 0.1.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (56) hide show
  1. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/.pre-commit-config.yaml +4 -1
  2. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/CHANGELOG.md +13 -3
  3. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/Cargo.lock +2 -2
  4. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/Cargo.toml +5 -2
  5. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/PKG-INFO +10 -3
  6. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/README.md +17 -3
  7. {git_bot_feedback-0.1.0/bindings/python → git_bot_feedback-0.2.1}/README.rst +9 -2
  8. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/bindings/python/CHANGELOG.md +41 -0
  9. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/bindings/python/Cargo.toml +2 -2
  10. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1/bindings/python}/README.rst +9 -2
  11. git_bot_feedback-0.2.1/src/client/common.rs +30 -0
  12. git_bot_feedback-0.2.1/src/client/gitea/mod.rs +302 -0
  13. git_bot_feedback-0.2.1/src/client/gitea/serde_structs.rs +105 -0
  14. git_bot_feedback-0.2.1/src/client/gitea/specific_api.rs +437 -0
  15. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/github/graphql.rs +2 -3
  16. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/github/mod.rs +15 -5
  17. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/github/serde_structs.rs +0 -27
  18. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/github/specific_api.rs +5 -9
  19. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/local.rs +1 -1
  20. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/client/mod.rs +50 -4
  21. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/error.rs +7 -1
  22. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/file_utils/file_filter.rs +1 -1
  23. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/file_utils/mod.rs +3 -0
  24. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/git_diff.rs +30 -12
  25. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/lib.rs +7 -0
  26. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/.gitattributes +0 -0
  27. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/.gitignore +0 -0
  28. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/CONTRIBUTING.md +0 -0
  29. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/LICENSE +0 -0
  30. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/bindings/python/git_bot_feedback.pyi +0 -0
  31. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/bindings/python/src/lib.rs +0 -0
  32. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/bindings/python/src/wrapper.rs +0 -0
  33. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/build.rs +0 -0
  34. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/codecov.yml +0 -0
  35. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/cspell.config.yml +0 -0
  36. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/client.rst +0 -0
  37. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/file-annotations.rst +0 -0
  38. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/file-changes.rst +0 -0
  39. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/index.rst +0 -0
  40. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/output-variables.rst +0 -0
  41. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/pr-reviews.rst +0 -0
  42. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/API/thread-comments.rst +0 -0
  43. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/_static/favicon.ico +0 -0
  44. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/_static/logo.png +0 -0
  45. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/conf.py +0 -0
  46. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/index.rst +0 -0
  47. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/docs/logo.xcf +0 -0
  48. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/git_bot_feedback.pyi +0 -0
  49. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/nurfile +0 -0
  50. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/pyproject.toml +0 -0
  51. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/comments/mod.rs +0 -0
  52. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/comments/review_comments.rs +0 -0
  53. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/comments/thread_comments.rs +0 -0
  54. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/file_annotations.rs +0 -0
  55. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/src/output_variable.rs +0 -0
  56. {git_bot_feedback-0.1.0 → git_bot_feedback-0.2.1}/uv.lock +0 -0
@@ -5,17 +5,20 @@ repos:
5
5
  rev: v6.0.0
6
6
  hooks:
7
7
  - id: trailing-whitespace
8
+ exclude: ^tests/assets/.*.diff$
8
9
  - id: end-of-file-fixer
10
+ exclude: ^tests/assets/.*.diff$
9
11
  - id: check-yaml
10
12
  - id: check-toml
11
13
  - id: check-added-large-files
12
14
  - id: mixed-line-ending
13
15
  args: ['--fix=lf']
16
+ exclude: ^tests/assets/.*.diff$
14
17
  - repo: https://github.com/streetsidesoftware/cspell-cli
15
18
  rev: v10.0.1
16
19
  hooks:
17
20
  - id: cspell
18
- exclude: CHANGELOG.md$
21
+ exclude: CHANGELOG.md$|.*\.diff$
19
22
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
23
  # Ruff version.
21
24
  rev: v0.15.20
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  <!-- markdownlint-disable MD024 -->
8
8
 
9
- ## [git-bot-feedback/v0.7.1] - 2026-06-29
9
+ ## [git-bot-feedback/v0.8.1] - 2026-07-14
10
10
 
11
11
  ### <!-- 1 --> 🚀 Added
12
12
 
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
25
25
  - Revise `FileFilter::walk_dir()` by @2bndy5 in [#78](https://github.com/2bndy5/git-bot-feedback/pull/78)
26
26
  - Strip parent path from `walk_dir()` results by @2bndy5 in [#83](https://github.com/2bndy5/git-bot-feedback/pull/83)
27
27
  - Add python bindings by @2bndy5 in [#90](https://github.com/2bndy5/git-bot-feedback/pull/90)
28
+ - Add Gitea support by @2bndy5 in [#94](https://github.com/2bndy5/git-bot-feedback/pull/94)
28
29
 
29
30
  ### <!-- 10 --> 💥 Breaking Changes
30
31
 
@@ -40,14 +41,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
41
  - Honor `ignore_index` when asserted by @2bndy5 in [#75](https://github.com/2bndy5/git-bot-feedback/pull/75)
41
42
  - Strip relative path from `FileFilter::walk_dir()` results by @2bndy5 in [#84](https://github.com/2bndy5/git-bot-feedback/pull/84)
42
43
  - Use correct name of PR review state that is "COMMENTED" by @2bndy5 in [#89](https://github.com/2bndy5/git-bot-feedback/pull/89)
44
+ - Fail on unsuccessful return code from request for file changes by @2bndy5 in [#95](https://github.com/2bndy5/git-bot-feedback/pull/95)
45
+ - Trim line endings from file names when parsing diff strings by @2bndy5 in [#97](https://github.com/2bndy5/git-bot-feedback/pull/97)
46
+ - Fail on parsing erroneous diff by @2bndy5 in [#98](https://github.com/2bndy5/git-bot-feedback/pull/98)
47
+ - Move option's value instead of cloning it by @2bndy5 in [#99](https://github.com/2bndy5/git-bot-feedback/pull/99)
48
+ - Output only diff using `git show` by @2bndy5 in [#102](https://github.com/2bndy5/git-bot-feedback/pull/102)
49
+
50
+ ### <!-- 8 --> 📝 Documentation
51
+
52
+ - Expand on some methods' doc comments by @2bndy5 in [#96](https://github.com/2bndy5/git-bot-feedback/pull/96)
43
53
 
44
54
  ### <!-- 9 --> 🗨️ Changed
45
55
 
46
56
  - Revise `FileAnnotations` impl by @2bndy5 in [#64](https://github.com/2bndy5/git-bot-feedback/pull/64)
47
57
 
48
- [git-bot-feedback/v0.7.1]: https://github.com/2bndy5/git-bot-feedback/compare/git-bot-feedback/v0.7.0...git-bot-feedback/v0.7.1
58
+ [git-bot-feedback/v0.8.1]: https://github.com/2bndy5/git-bot-feedback/compare/git-bot-feedback/v0.8.0...git-bot-feedback/v0.8.1
49
59
 
50
- Full commit diff: [`git-bot-feedback/v0.7.0...git-bot-feedback/v0.7.1`][git-bot-feedback/v0.7.1]
60
+ Full commit diff: [`git-bot-feedback/v0.8.0...git-bot-feedback/v0.8.1`][git-bot-feedback/v0.8.1]
51
61
 
52
62
 
53
63
  <!-- generated by git-cliff -->
@@ -400,7 +400,7 @@ dependencies = [
400
400
 
401
401
  [[package]]
402
402
  name = "git-bot-feedback"
403
- version = "0.7.1"
403
+ version = "0.8.1"
404
404
  dependencies = [
405
405
  "async-trait",
406
406
  "chrono",
@@ -420,7 +420,7 @@ dependencies = [
420
420
 
421
421
  [[package]]
422
422
  name = "git-bot-feedback-py"
423
- version = "0.1.0"
423
+ version = "0.2.1"
424
424
  dependencies = [
425
425
  "git-bot-feedback",
426
426
  "pyo3",
@@ -21,7 +21,7 @@ features = ["http2", "charset", "system-proxy"]
21
21
  [package]
22
22
  name = "git-bot-feedback"
23
23
  description = "A library designed for CI tools that posts comments on a Pull Request."
24
- version = "0.7.1"
24
+ version = "0.8.1"
25
25
  edition.workspace = true
26
26
  license.workspace = true
27
27
  repository.workspace = true
@@ -58,8 +58,11 @@ pyo3 = ["dep:pyo3"]
58
58
  # GitHub implementation/support
59
59
  github = []
60
60
 
61
+ # Gitea implementation/support
62
+ gitea = []
63
+
61
64
  # features enabled by default
62
- default = ["github"]
65
+ default = ["github", "gitea"]
63
66
 
64
67
  # optional feature to silence a compiler error if/when
65
68
  # no features enable any git server implementations
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-bot-feedback
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: System Administrators
@@ -51,15 +51,22 @@ loop is required.
51
51
  Supported git servers
52
52
  ---------------------
53
53
 
54
- Initially, this project is designed to work with GitHub.
55
- But the API is designed to easily add support for other git servers.
54
+ This project is designed to easily add support for various git servers.
56
55
  The following is just a list of git servers that are planned (in order or priority).
57
56
 
58
57
  - GitHub
59
58
  - GitLab
60
59
  - Gitea
60
+
61
+ Gitea does not support
62
+
63
+ - posting thread comments for commits (push events)
64
+ - programmatically deleting a PR reviews' individual comments, rather we can
65
+ only resolve them (currently). However, deleting an entire PR review is supported.
61
66
  - BitBucket
62
67
 
68
+ Currently, only Github and Gitea are supported.
69
+
63
70
  LGPL license
64
71
  ------------
65
72
 
@@ -32,15 +32,29 @@ consumers to choose the TLS backend of their choice; see [reqwest's features][re
32
32
 
33
33
  ## Supported git servers
34
34
 
35
- Initially this project os designed to work with GitHub.
36
- But the API is designed to easily add support for other git servers.
35
+ This project is designed to easily add support for various git servers.
37
36
  The following is just a list of git servers that are planned (in order or priority).
38
37
 
39
38
  - [x] GitHub
40
39
  - [ ] GitLab
41
- - [ ] Gitea
40
+ - [x] Gitea
41
+
42
+ Gitea does not support
43
+
44
+ - posting thread comments for commits (push events)
45
+ - programmatically deleting a PR reviews' individual comments,
46
+ rather we can only resolve them (currently).
47
+ However, deleting an entire PR review is supported.
42
48
  - [ ] BitBucket
43
49
 
50
+ ### Optional support
51
+
52
+ Each supported implementation of the above git servers can be controlled via
53
+ [cargo features][dep-features]. They are enabled by default.
54
+
55
+ - `github` enables support of GitHub implementation
56
+ - `gitea` enables support of Gitea implementation
57
+
44
58
  ## LGPL license
45
59
 
46
60
  This project is licensed under [LGPL-3.0-or-later].
@@ -26,15 +26,22 @@ loop is required.
26
26
  Supported git servers
27
27
  ---------------------
28
28
 
29
- Initially, this project is designed to work with GitHub.
30
- But the API is designed to easily add support for other git servers.
29
+ This project is designed to easily add support for various git servers.
31
30
  The following is just a list of git servers that are planned (in order or priority).
32
31
 
33
32
  - GitHub
34
33
  - GitLab
35
34
  - Gitea
35
+
36
+ Gitea does not support
37
+
38
+ - posting thread comments for commits (push events)
39
+ - programmatically deleting a PR reviews' individual comments, rather we can
40
+ only resolve them (currently). However, deleting an entire PR review is supported.
36
41
  - BitBucket
37
42
 
43
+ Currently, only Github and Gitea are supported.
44
+
38
45
  LGPL license
39
46
  ------------
40
47
 
@@ -6,11 +6,52 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  <!-- markdownlint-disable MD024 -->
8
8
 
9
+ ## [git-bot-feedback-py/v0.2.1] - 2026-07-14
10
+
11
+ ### <!-- 4 --> 🛠️ Fixed
12
+
13
+ - Output only diff using `git show` by @2bndy5 in [#102](https://github.com/2bndy5/git-bot-feedback/pull/102)
14
+
15
+ ### <!-- 6 --> 📦 Dependency updates
16
+
17
+ - Bump version to git-bot-feedback/v0.8.1 by @2bndy5 in [`71dac26`](https://github.com/2bndy5/git-bot-feedback/commit/71dac265a5919e02ec2a6c54ee3dd1bebfb5d156)
18
+
19
+ [git-bot-feedback-py/v0.2.1]: https://github.com/2bndy5/git-bot-feedback/compare/git-bot-feedback-py/v0.2.0...git-bot-feedback-py/v0.2.1
20
+
21
+ Full commit diff: [`git-bot-feedback-py/v0.2.0...git-bot-feedback-py/v0.2.1`][git-bot-feedback-py/v0.2.1]
22
+
23
+ ## [git-bot-feedback-py/v0.2.0] - 2026-07-01
24
+
25
+ ### <!-- 1 --> 🚀 Added
26
+
27
+ - Add Gitea support by @2bndy5 in [#94](https://github.com/2bndy5/git-bot-feedback/pull/94)
28
+
29
+ ### <!-- 4 --> 🛠️ Fixed
30
+
31
+ - Fail on unsuccessful return code from request for file changes by @2bndy5 in [#95](https://github.com/2bndy5/git-bot-feedback/pull/95)
32
+ - Trim line endings from file names when parsing diff strings by @2bndy5 in [#97](https://github.com/2bndy5/git-bot-feedback/pull/97)
33
+ - Fail on parsing erroneous diff by @2bndy5 in [#98](https://github.com/2bndy5/git-bot-feedback/pull/98)
34
+ - Move option's value instead of cloning it by @2bndy5 in [#99](https://github.com/2bndy5/git-bot-feedback/pull/99)
35
+
36
+ ### <!-- 6 --> 📦 Dependency updates
37
+
38
+ - Bump version to git-bot-feedback/v0.8.0 by @2bndy5 in [`d6070e7`](https://github.com/2bndy5/git-bot-feedback/commit/d6070e7d41cdc8a1c01c5f3a69a16aa8bdff0d25)
39
+ - Bump version to git-bot-feedback-py/v0.2.0 by @2bndy5 in [`17d58f4`](https://github.com/2bndy5/git-bot-feedback/commit/17d58f4b2e4b2f9cd229584648dd35f3a2d736f7)
40
+
41
+ ### <!-- 8 --> 📝 Documentation
42
+
43
+ - Expand on some methods' doc comments by @2bndy5 in [#96](https://github.com/2bndy5/git-bot-feedback/pull/96)
44
+
45
+ [git-bot-feedback-py/v0.2.0]: https://github.com/2bndy5/git-bot-feedback/compare/git-bot-feedback-py/v0.1.0...git-bot-feedback-py/v0.2.0
46
+
47
+ Full commit diff: [`git-bot-feedback-py/v0.1.0...git-bot-feedback-py/v0.2.0`][git-bot-feedback-py/v0.2.0]
48
+
9
49
  ## [git-bot-feedback-py/v0.1.0] - 2026-06-29
10
50
 
11
51
  ### <!-- 6 --> 📦 Dependency updates
12
52
 
13
53
  - Bump version to git-bot-feedback/v0.7.1 by @2bndy5 in [`5011a04`](https://github.com/2bndy5/git-bot-feedback/commit/5011a04d4806b08e54a1ed2ba456badb7622e682)
54
+ - Bump version to git-bot-feedback-py/v0.1.0 by @2bndy5 in [`c02c181`](https://github.com/2bndy5/git-bot-feedback/commit/c02c181b8ef8f5c3f825c334e02af2765c2a5cea)
14
55
 
15
56
  ### <!-- 9 --> 🗨️ Changed
16
57
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "git-bot-feedback-py"
3
- version = "0.1.0"
3
+ version = "0.2.1"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -14,7 +14,7 @@ crate-type = ["cdylib"] # spell-checker: disable-line
14
14
  path = "src/lib.rs"
15
15
 
16
16
  [dependencies]
17
- git-bot-feedback = { path = "../..", version = "0.7.1", features = ["file-changes", "pyo3"] }
17
+ git-bot-feedback = { path = "../..", version = "0.8.1", features = ["file-changes", "pyo3"] }
18
18
  pyo3 = { workspace = true }
19
19
  pyo3-async-runtimes = { version = "0.29.0", features = ["tokio-runtime"] }
20
20
  pyo3-log = "0.13.4"
@@ -26,15 +26,22 @@ loop is required.
26
26
  Supported git servers
27
27
  ---------------------
28
28
 
29
- Initially, this project is designed to work with GitHub.
30
- But the API is designed to easily add support for other git servers.
29
+ This project is designed to easily add support for various git servers.
31
30
  The following is just a list of git servers that are planned (in order or priority).
32
31
 
33
32
  - GitHub
34
33
  - GitLab
35
34
  - Gitea
35
+
36
+ Gitea does not support
37
+
38
+ - posting thread comments for commits (push events)
39
+ - programmatically deleting a PR reviews' individual comments, rather we can
40
+ only resolve them (currently). However, deleting an entire PR review is supported.
36
41
  - BitBucket
37
42
 
43
+ Currently, only Github and Gitea are supported.
44
+
38
45
  LGPL license
39
46
  ------------
40
47
 
@@ -0,0 +1,30 @@
1
+ #![cfg(any(feature = "gitea", feature = "github"))]
2
+
3
+ use serde::Deserialize;
4
+
5
+ #[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
6
+ #[serde(rename_all = "lowercase")]
7
+ pub enum PullRequestState {
8
+ Open,
9
+ Closed,
10
+ }
11
+
12
+ /// PR event payload.
13
+ #[derive(Debug, Deserialize, PartialEq, Clone)]
14
+ pub struct PullRequestEventPayload {
15
+ /// The Pull Request's info.
16
+ pub pull_request: PullRequestInfo,
17
+ }
18
+
19
+ /// A structure for deserializing a Pull Request's info from a response's json.
20
+ #[derive(Debug, Deserialize, PartialEq, Clone)]
21
+ pub struct PullRequestInfo {
22
+ /// Is this PR a draft?
23
+ pub draft: bool,
24
+ /// Is this PR locked?
25
+ pub locked: bool,
26
+ /// The Pull Request's number.
27
+ pub number: u64,
28
+ /// What is current state of this PR?
29
+ pub state: PullRequestState,
30
+ }
@@ -0,0 +1,302 @@
1
+ use std::{env, fs::OpenOptions, io::Write};
2
+
3
+ use async_trait::async_trait;
4
+ use reqwest::{Client, Method};
5
+ use url::Url;
6
+
7
+ use super::{ClientError, RestApiClient, RestApiRateLimitHeaders, common::PullRequestInfo};
8
+ use crate::{
9
+ OutputVariable, ReviewAction, ReviewOptions, ThreadCommentOptions,
10
+ client::common::PullRequestState,
11
+ };
12
+ mod serde_structs;
13
+ use serde_structs::{FullReview, ReviewDiffComment};
14
+ mod specific_api;
15
+
16
+ #[cfg(feature = "file-changes")]
17
+ use crate::{FileDiffLines, FileFilter, LinesChangedOnly, parse_diff};
18
+ #[cfg(feature = "file-changes")]
19
+ use reqwest::header::{HeaderMap, HeaderValue};
20
+ #[cfg(feature = "file-changes")]
21
+ use std::collections::HashMap;
22
+
23
+ /// A structure to work with Gitea REST API.
24
+ pub struct GiteaApiClient {
25
+ /// The HTTP request client to be used for all REST API calls.
26
+ client: Client,
27
+
28
+ /// The CI run's event payload from the webhook that triggered the workflow.
29
+ pull_request: Option<PullRequestInfo>,
30
+
31
+ /// The name of the event that was triggered when running cpp_linter.
32
+ pub event_name: String,
33
+
34
+ /// The value of the `GITHUB_API_URL` environment variable.
35
+ api_url: Url,
36
+
37
+ /// The value of the `GITHUB_REPOSITORY` environment variable.
38
+ repo: String,
39
+
40
+ /// The value of the `GITHUB_SHA` environment variable.
41
+ sha: String,
42
+
43
+ /// The value of the `ACTIONS_STEP_DEBUG` environment variable.
44
+ pub debug_enabled: bool,
45
+
46
+ /// The response header names that describe the rate limit status.
47
+ rate_limit_headers: RestApiRateLimitHeaders,
48
+ }
49
+
50
+ #[async_trait]
51
+ impl RestApiClient for GiteaApiClient {
52
+ fn start_log_group(&self, name: &str) {
53
+ log::info!(target: "CI_LOG_GROUPING", "::group::{name}");
54
+ }
55
+
56
+ fn end_log_group(&self, _name: &str) {
57
+ log::info!(target: "CI_LOG_GROUPING", "::endgroup::");
58
+ }
59
+
60
+ fn is_pr_event(&self) -> bool {
61
+ self.pull_request.is_some()
62
+ }
63
+
64
+ fn set_user_agent(&mut self, user_agent: &str) -> Result<(), ClientError> {
65
+ self.client = Client::builder()
66
+ .default_headers(Self::make_headers()?)
67
+ .user_agent(user_agent)
68
+ .build()?;
69
+ Ok(())
70
+ }
71
+
72
+ /// Does not support push events, only PR events.
73
+ async fn post_thread_comment(&self, options: ThreadCommentOptions) -> Result<(), ClientError> {
74
+ let comments_url = match &self.pull_request {
75
+ Some(pr_info) => {
76
+ if pr_info.locked {
77
+ return Ok(()); // cannot comment on locked PRs
78
+ }
79
+ env::var("GITEA_TOKEN").map_err(|e| ClientError::env_var("GITEA_TOKEN", e))?;
80
+ self.api_url.join(
81
+ format!("repos/{}/issues/{}/comments", self.repo, pr_info.number).as_str(),
82
+ )?
83
+ }
84
+ None => {
85
+ // This feature is supported in non-PR events on other git servers.
86
+ // But Gitea only supports comments on PRs or issues.
87
+ // Leave a informative log entry to highlight this and return early.
88
+ log::info!(
89
+ "Gitea support for posting thread comments is limited to pull requests only."
90
+ );
91
+ return Ok(());
92
+ }
93
+ };
94
+ self.update_comment(comments_url, options).await
95
+ }
96
+
97
+ async fn cull_pr_reviews(&mut self, options: &mut ReviewOptions) -> Result<(), ClientError> {
98
+ if let Some(pr_info) = self.pull_request.as_ref() {
99
+ // Guard checks for unsuitable PR states
100
+ if (!options.allow_draft && pr_info.draft)
101
+ || (!options.allow_closed && pr_info.state == PullRequestState::Closed)
102
+ || pr_info.locked
103
+ {
104
+ return Ok(());
105
+ }
106
+
107
+ // Fetch existing reviews from this bot
108
+ let existing_reviews = self
109
+ .get_existing_review_comments(pr_info.number as i64, &options.marker)
110
+ .await?;
111
+
112
+ if existing_reviews.is_empty() {
113
+ return Ok(());
114
+ }
115
+
116
+ let mut outdated_comment_ids = Vec::new();
117
+ let mut outdated_review_ids = Vec::new();
118
+ let mut reused_comments = std::collections::HashSet::new();
119
+
120
+ // Check each existing review for reused comments
121
+ for review in &existing_reviews {
122
+ let mut keep_review = false;
123
+
124
+ for existing_comment in &review.comments {
125
+ let mut keep_comment = false;
126
+
127
+ // Try to match against proposed comments
128
+ for proposed_comment in options.comments.iter() {
129
+ if Self::match_review_comment(
130
+ existing_comment,
131
+ proposed_comment,
132
+ &options.marker,
133
+ ) {
134
+ log::info!(
135
+ "Using existing review comment: path='{}', line_end={}",
136
+ existing_comment.path,
137
+ existing_comment.new_position
138
+ );
139
+ reused_comments.insert(proposed_comment.clone());
140
+ keep_comment = true;
141
+ keep_review = true;
142
+ break;
143
+ }
144
+ }
145
+
146
+ // If comment doesn't match any proposed comment, mark for deletion
147
+ if !keep_comment {
148
+ outdated_comment_ids.push(existing_comment.id);
149
+ }
150
+ }
151
+
152
+ // If no comments in this review were kept, mark review for deletion
153
+ if !keep_review {
154
+ outdated_review_ids.push(review.id);
155
+ }
156
+ }
157
+
158
+ // Remove reused comments from proposed comments
159
+ options.comments.retain(|c| !reused_comments.contains(c));
160
+
161
+ // Delete outdated comments and reviews
162
+ if !outdated_comment_ids.is_empty() || !outdated_review_ids.is_empty() {
163
+ self.delete_outdated_review_comments(
164
+ pr_info.number as i64,
165
+ outdated_comment_ids,
166
+ outdated_review_ids,
167
+ options.delete_review_comments,
168
+ )
169
+ .await?;
170
+ }
171
+ }
172
+ Ok(())
173
+ }
174
+
175
+ async fn post_pr_review(&mut self, options: &ReviewOptions) -> Result<(), ClientError> {
176
+ if let Some(pr_info) = self.pull_request.as_ref() {
177
+ if (!options.allow_draft && pr_info.draft)
178
+ || (!options.allow_closed && pr_info.state == PullRequestState::Closed)
179
+ || pr_info.locked
180
+ {
181
+ return Ok(());
182
+ }
183
+ env::var("GITEA_TOKEN").map_err(|e| ClientError::env_var("GITEA_TOKEN", e))?;
184
+ let url = self
185
+ .api_url
186
+ .join(format!("repos/{}/pulls/{}/reviews", self.repo, pr_info.number).as_str())?;
187
+ let payload = FullReview {
188
+ event: match options.action {
189
+ ReviewAction::Comment => String::from("COMMENT"),
190
+ ReviewAction::Approve => String::from("APPROVED"),
191
+ ReviewAction::RequestChanges => String::from("REQUEST_CHANGES"),
192
+ },
193
+ body: format!("{}{}", options.marker, options.summary),
194
+ comments: options
195
+ .comments
196
+ .iter()
197
+ .map(ReviewDiffComment::from)
198
+ .map(|mut r| {
199
+ if !r.body.starts_with(&options.marker) {
200
+ r.body = format!("{}{}", options.marker, r.body);
201
+ }
202
+ r
203
+ })
204
+ .collect(),
205
+ commit_id: self.sha.clone(),
206
+ };
207
+ let request = self.make_api_request(
208
+ &self.client,
209
+ url,
210
+ Method::POST,
211
+ Some(
212
+ serde_json::to_string(&payload)
213
+ .map_err(|e| ClientError::json("serialize PR review payload", e))?,
214
+ ),
215
+ None,
216
+ )?;
217
+ let response = self
218
+ .send_api_request(&self.client, request, &self.rate_limit_headers)
219
+ .await;
220
+ match response {
221
+ Ok(response) => {
222
+ self.log_response(response, "Failed to post PR review")
223
+ .await;
224
+ }
225
+ Err(e) => {
226
+ return Err(e.add_request_context("post PR review"));
227
+ }
228
+ }
229
+ }
230
+ Ok(())
231
+ }
232
+
233
+ fn write_output_variables(&self, vars: &[OutputVariable]) -> Result<(), ClientError> {
234
+ if vars.is_empty() {
235
+ // Should probably be an error. This check is only here to prevent needlessly
236
+ // fetching the env var GITEA_OUTPUT value and opening the referenced file.
237
+ return Ok(());
238
+ }
239
+ if let Ok(gh_out) = env::var("GITEA_OUTPUT") {
240
+ return match OpenOptions::new().append(true).open(gh_out) {
241
+ Ok(mut gh_out_file) => {
242
+ for out_var in vars {
243
+ out_var.validate()?;
244
+ writeln!(&mut gh_out_file, "{}={}\n", out_var.name, out_var.value)
245
+ .map_err(|e| ClientError::io("write to GITEA_OUTPUT file", e))?;
246
+ }
247
+ Ok(())
248
+ }
249
+ Err(e) => Err(ClientError::io("write to GITEA_OUTPUT file", e)),
250
+ };
251
+ }
252
+ Ok(())
253
+ }
254
+
255
+ fn append_step_summary(&self, comment: &str) -> Result<(), ClientError> {
256
+ if let Ok(gh_out) = env::var("GITEA_STEP_SUMMARY") {
257
+ // step summary MD file can be overwritten/removed in CI runners
258
+ return match OpenOptions::new().append(true).open(gh_out) {
259
+ Ok(mut gh_out_file) => {
260
+ let result = writeln!(&mut gh_out_file, "\n{comment}\n");
261
+ result.map_err(|e| ClientError::io("write to GITHUB_STEP_SUMMARY file", e))
262
+ }
263
+ Err(e) => Err(ClientError::io("write to GITHUB_STEP_SUMMARY file", e)),
264
+ };
265
+ }
266
+ Ok(())
267
+ }
268
+
269
+ #[cfg(feature = "file-changes")]
270
+ #[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
271
+ async fn get_list_of_changed_files(
272
+ &self,
273
+ file_filter: &FileFilter,
274
+ lines_changed_only: &LinesChangedOnly,
275
+ _base_diff: Option<String>,
276
+ _ignore_index: bool,
277
+ ) -> Result<HashMap<String, FileDiffLines>, ClientError> {
278
+ let url_path = match &self.pull_request {
279
+ Some(pr_info) => format!("repos/{}/pulls/{}.diff", self.repo, pr_info.number),
280
+ None => format!("repos/{}/commits/{}.diff", self.repo, self.sha),
281
+ };
282
+ let url = self.api_url.join(&url_path)?;
283
+ let mut headers = HeaderMap::new();
284
+ headers.insert("Accept", HeaderValue::from_str("text/plain")?);
285
+ let request = self.make_api_request(&self.client, url, Method::GET, None, Some(headers))?;
286
+ let response = self
287
+ .send_api_request(&self.client, request, &self.rate_limit_headers)
288
+ .await?;
289
+ if let Err(e) = response.error_for_status_ref() {
290
+ if let Ok(body) = response.text().await {
291
+ log::error!("Failed to get list of changed files: {e:?}\n{body}");
292
+ }
293
+ return Err(ClientError::Request(e).add_request_context("get list of changed files"));
294
+ }
295
+ let body = (response.text()).await?.to_string();
296
+ parse_diff(&body, file_filter, lines_changed_only).map_err(ClientError::DiffError)
297
+ }
298
+
299
+ fn client_kind(&self) -> String {
300
+ "gitea".to_string()
301
+ }
302
+ }