Use Forgejo-compatible repository contents API

This commit is contained in:
Georg Bauer
2026-08-03 18:29:10 +02:00
parent d47b21d6a6
commit 7376e6cee3
3 changed files with 59 additions and 15 deletions

View File

@@ -11,9 +11,10 @@ relationships, while
Generated API modules and client configuration are deliberately private so the Generated API modules and client configuration are deliberately private so the
CLI and app cannot duplicate Gitea behavior. CLI and app cannot duplicate Gitea behavior.
Forgejo uses the common generated client for its documented Gitea-compatible Forgejo uses operations from its advertised Gitea 1.22-compatible `/api/v1`
`/api/v1` surface. Provider discovery and provider-specific endpoints, such as surface. Gotcha must not assume that newer operations from its generated Gitea
Forgejo's dedicated version API, are routed inside `gotcha_gitea::Client`. 1.25 client exist on Forgejo: provider-specific endpoints and any operation
outside the shared contract are routed inside `gotcha_gitea::Client`.
| Domain | Queries | Other actions | Generated client | CLI queries | CLI writes | Capabilities | | Domain | Queries | Other actions | Generated client | CLI queries | CLI writes | Capabilities |
| --- | ---: | ---: | --- | ---: | ---: | --- | | --- | ---: | ---: | --- | ---: | ---: | --- |

View File

@@ -6,7 +6,7 @@ use std::{
use tokio::task::JoinSet; use tokio::task::JoinSet;
use crate::{ use crate::{
Client, Error, Result, Client, Error, Method, Result,
domain::{DEFAULT_PAGE_SIZE, HistoryCommit, Page, RepositoryId}, domain::{DEFAULT_PAGE_SIZE, HistoryCommit, Page, RepositoryId},
models, validate_page, models, validate_page,
}; };
@@ -95,17 +95,17 @@ impl Client {
.await .await
.map_err(Error::generated) .map_err(Error::generated)
} else { } else {
apis::repository_api::repo_get_contents_ext( let endpoint = format!(
&configuration, "repos/{}/{}/contents/{}",
&repository.owner, apis::urlencode(&repository.owner),
&repository.repository, apis::urlencode(&repository.repository),
path, apis::urlencode(path),
None, );
None, self.execute(self.request(Method::GET, &endpoint)?)
) .await?
.await .json()
.map(|contents| contents.dir_contents.unwrap_or_default()) .await
.map_err(Error::generated) .map_err(Into::into)
} }
} }

View File

@@ -1,4 +1,9 @@
use super::*; use super::*;
use std::{
io::{Read, Write},
net::TcpListener,
thread,
};
fn commit(sha: &str, parents: &[&str]) -> models::Commit { fn commit(sha: &str, parents: &[&str]) -> models::Commit {
models::Commit { models::Commit {
@@ -95,3 +100,41 @@ fn labels_first_commit_of_pull_branch() {
.contains(&"feature".into()) .contains(&"feature".into())
); );
} }
#[tokio::test]
async fn reads_nested_directory_from_compatible_contents_endpoint() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut buffer = [0; 4096];
let length = stream.read(&mut buffer).unwrap();
let request = String::from_utf8_lossy(&buffer[..length]).into_owned();
let body = r#"[{"name":"guide.md","path":"docs/guide.md","type":"file"}]"#;
write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
)
.unwrap();
request
});
let client =
Client::with_provider(&format!("http://{address}"), None, crate::Provider::Forgejo)
.unwrap();
let repository = RepositoryId::new("forgejo", "forgejo").unwrap();
let contents = client
.repository_contents(&repository, "docs/guide")
.await
.unwrap();
assert_eq!(contents.len(), 1);
assert_eq!(contents[0].path.as_deref(), Some("docs/guide.md"));
assert!(
server
.join()
.unwrap()
.starts_with("GET /api/v1/repos/forgejo/forgejo/contents/docs%2Fguide ")
);
}