Skip to content

Commit 50ec7f0

Browse files
committed
repo info end point
1 parent 98830bf commit 50ec7f0

File tree

11 files changed

+298
-3
lines changed

11 files changed

+298
-3
lines changed

openapi/openapi.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,25 @@ paths:
334334
items:
335335
$ref: '#/components/schemas/Assignee'
336336

337+
/repo:
338+
get:
339+
summary: Get repository information
340+
operationId: getRepoInfo
341+
tags: [status]
342+
description: |
343+
Returns information about the current repository including local and remote commits.
344+
The remote_commit represents:
345+
- When Clean: Same as local_commit (repository is up to date)
346+
- When Ahead: The last common ancestor before local commits
347+
- When Behind/Diverged: The latest remote commit
348+
responses:
349+
'200':
350+
description: Repository information
351+
content:
352+
application/json:
353+
schema:
354+
$ref: '#/components/schemas/RepoInfo'
355+
337356
/configuration/checklists:
338357
get:
339358
summary: List available checklists
@@ -784,6 +803,38 @@ components:
784803
type: string
785804
nullable: true
786805

806+
RepoInfo:
807+
type: object
808+
required:
809+
- owner
810+
- repo
811+
- branch
812+
- local_commit
813+
- remote_commit
814+
- git_status
815+
- git_status_detail
816+
properties:
817+
owner:
818+
type: string
819+
description: Repository owner/organization
820+
repo:
821+
type: string
822+
description: Repository name
823+
branch:
824+
type: string
825+
description: Current branch name
826+
local_commit:
827+
type: string
828+
description: Current local commit hash
829+
remote_commit:
830+
type: string
831+
description: Remote tracking commit hash (base ancestor when ahead, latest remote when behind)
832+
git_status:
833+
$ref: '#/components/schemas/GitStatusEnum'
834+
git_status_detail:
835+
type: string
836+
description: Human-readable git status description
837+
787838
Checklist:
788839
type: object
789840
properties:

src/api/routes/status.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::api::error::ApiError;
44
use crate::api::state::AppState;
5-
use crate::api::types::Assignee;
5+
use crate::api::types::{Assignee, RepoInfoResponse};
66
use crate::{GitProvider, get_repo_users};
77
use axum::{Json, extract::State};
88

@@ -22,3 +22,9 @@ pub async fn list_assignees<G: GitProvider + 'static>(
2222

2323
Ok(Json(response))
2424
}
25+
26+
pub async fn repo_info<G: GitProvider + 'static>(
27+
State(state): State<AppState<G>>,
28+
) -> Result<Json<RepoInfoResponse>, ApiError> {
29+
RepoInfoResponse::new(state.git_info()).map(Json)
30+
}

src/api/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub fn create_router<G: GitProvider + 'static>(state: AppState<G>) -> Router {
5050
.route("/api/issues/{number}/review", post(comments::review_issue))
5151
// Supporting Data
5252
.route("/api/assignees", get(status::list_assignees))
53+
.route("/api/repo", get(status::repo_info))
5354
// Configuration
5455
.route(
5556
"/api/configuration/checklists",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: "GET /api/repo - ahead status"
2+
description: "Repository is ahead, remote_commit is the base commit (last in ahead list)"
3+
4+
fixtures:
5+
milestones:
6+
- v1.0.json
7+
8+
git_state:
9+
commit: "abc1234567890abcdef1234567890abcdef1234"
10+
branch: "main"
11+
status:
12+
type: ahead
13+
commits:
14+
- "def5678901234567890abcdef1234567890abcdef"
15+
- "9012345678901234567890abcdef1234567890ab"
16+
17+
request:
18+
method: GET
19+
path: "/api/repo"
20+
21+
response:
22+
status: 200
23+
body:
24+
match_type: exact
25+
value:
26+
owner: "test-owner"
27+
repo: "test-repo"
28+
branch: "main"
29+
local_commit: "abc1234567890abcdef1234567890abcdef1234"
30+
remote_commit: "9012345678901234567890abcdef1234567890ab"
31+
git_status: "ahead"
32+
git_status_detail: "Repository is ahead by 2 commits"
33+
34+
assert_write_calls: []
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: "GET /api/repo - behind status"
2+
description: "Repository is behind, remote_commit is the latest remote commit (first in behind list)"
3+
4+
fixtures:
5+
milestones:
6+
- v1.0.json
7+
8+
git_state:
9+
commit: "abc1234567890abcdef1234567890abcdef1234"
10+
branch: "main"
11+
status:
12+
type: behind
13+
commits:
14+
- "3456789012345678901234567890abcdef123456"
15+
- "7890123456789012345678901234567890abcdef"
16+
17+
request:
18+
method: GET
19+
path: "/api/repo"
20+
21+
response:
22+
status: 200
23+
body:
24+
match_type: exact
25+
value:
26+
owner: "test-owner"
27+
repo: "test-repo"
28+
branch: "main"
29+
local_commit: "abc1234567890abcdef1234567890abcdef1234"
30+
remote_commit: "3456789012345678901234567890abcdef123456"
31+
git_status: "behind"
32+
git_status_detail: "Repository is behind by 2 commits"
33+
34+
assert_write_calls: []
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: "GET /api/repo - clean status"
2+
description: "Repository is up to date, remote_commit equals local_commit"
3+
4+
fixtures:
5+
milestones:
6+
- v1.0.json
7+
8+
git_state:
9+
commit: "abc1234567890abcdef1234567890abcdef1234"
10+
branch: "main"
11+
status:
12+
type: clean
13+
14+
request:
15+
method: GET
16+
path: "/api/repo"
17+
18+
response:
19+
status: 200
20+
body:
21+
match_type: exact
22+
value:
23+
owner: "test-owner"
24+
repo: "test-repo"
25+
branch: "main"
26+
local_commit: "abc1234567890abcdef1234567890abcdef1234"
27+
remote_commit: "abc1234567890abcdef1234567890abcdef1234"
28+
git_status: "clean"
29+
git_status_detail: "Repository is up to date!"
30+
31+
assert_write_calls: []
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: "GET /api/repo - diverged status"
2+
description: "Repository is diverged, remote_commit is the latest remote commit (first in behind list)"
3+
4+
fixtures:
5+
milestones:
6+
- v1.0.json
7+
8+
git_state:
9+
commit: "abc1234567890abcdef1234567890abcdef1234"
10+
branch: "main"
11+
status:
12+
type: diverged
13+
ahead:
14+
- "def5678901234567890abcdef1234567890abcdef"
15+
- "9012345678901234567890abcdef1234567890ab"
16+
behind:
17+
- "4567890123456789012345678901234567890abc"
18+
- "8901234567890123456789012345678901234567"
19+
20+
request:
21+
method: GET
22+
path: "/api/repo"
23+
24+
response:
25+
status: 200
26+
body:
27+
match_type: exact
28+
value:
29+
owner: "test-owner"
30+
repo: "test-repo"
31+
branch: "main"
32+
local_commit: "abc1234567890abcdef1234567890abcdef1234"
33+
remote_commit: "4567890123456789012345678901234567890abc"
34+
git_status: "diverged"
35+
git_status_detail: "Repository is ahead by 2 and behind by 2 commits"
36+
37+
assert_write_calls: []

src/api/tests/harness/mock_builder.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use std::path::PathBuf;
2+
use std::str::FromStr;
23

34
use super::loader::LoadedFixtures;
4-
use super::types::GitState;
5+
use super::types::{GitState, GitStatusSpec};
56
use crate::api::tests::helpers::MockGitInfo;
7+
use crate::GitStatus;
8+
use gix::ObjectId;
69

710
/// Builds MockGitInfo from test specification
811
pub struct MockBuilder;
@@ -46,6 +49,39 @@ impl MockBuilder {
4649
builder = builder.with_dirty_file(PathBuf::from(file));
4750
}
4851

52+
// Set git status
53+
if let Some(status_spec) = &git_state.status {
54+
builder = builder.with_status(Self::convert_status(status_spec));
55+
}
56+
4957
builder.build()
5058
}
59+
60+
/// Convert GitStatusSpec to GitStatus
61+
fn convert_status(spec: &GitStatusSpec) -> GitStatus {
62+
match spec {
63+
GitStatusSpec::Clean => GitStatus::Clean,
64+
GitStatusSpec::Ahead { commits } => {
65+
GitStatus::Ahead(Self::parse_object_ids(commits))
66+
}
67+
GitStatusSpec::Behind { commits } => {
68+
GitStatus::Behind(Self::parse_object_ids(commits))
69+
}
70+
GitStatusSpec::Diverged { ahead, behind } => GitStatus::Diverged {
71+
ahead: Self::parse_object_ids(ahead),
72+
behind: Self::parse_object_ids(behind),
73+
},
74+
}
75+
}
76+
77+
/// Parse commit hash strings into ObjectIds
78+
fn parse_object_ids(hashes: &[String]) -> Vec<ObjectId> {
79+
hashes
80+
.iter()
81+
.map(|h| {
82+
ObjectId::from_str(h)
83+
.unwrap_or_else(|_| ObjectId::empty_tree(gix::hash::Kind::Sha1))
84+
})
85+
.collect()
86+
}
5187
}

src/api/tests/harness/types.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ pub struct GitState {
6868
/// Dirty files in working directory
6969
#[serde(default)]
7070
pub dirty_files: Vec<String>,
71+
/// Git repository status (ahead/behind/diverged/clean)
72+
#[serde(default)]
73+
pub status: Option<GitStatusSpec>,
74+
}
75+
76+
/// Git status specification for tests
77+
#[derive(Debug, Clone, Deserialize, Serialize)]
78+
#[serde(tag = "type", rename_all = "snake_case")]
79+
pub enum GitStatusSpec {
80+
Clean,
81+
Ahead { commits: Vec<String> },
82+
Behind { commits: Vec<String> },
83+
Diverged {
84+
ahead: Vec<String>,
85+
behind: Vec<String>,
86+
},
7187
}
7288

7389
impl Default for GitState {
@@ -78,6 +94,7 @@ impl Default for GitState {
7894
commit: default_commit(),
7995
branch: default_branch(),
8096
dirty_files: Vec::new(),
97+
status: None,
8198
}
8299
}
83100
}

src/api/tests/helpers.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub struct MockGitInfo {
5151

5252
// Status
5353
dirty_files: Arc<Mutex<Vec<PathBuf>>>,
54+
git_status: GitStatus,
5455

5556
// Call tracking (for assertions)
5657
calls: Arc<Mutex<Vec<String>>>,
@@ -80,6 +81,7 @@ pub struct MockGitInfoBuilder {
8081
milestones: Vec<octocrab::models::Milestone>,
8182
users: Vec<crate::RepoUser>,
8283
dirty_files: Vec<PathBuf>,
84+
git_status: GitStatus,
8385
}
8486

8587
impl MockGitInfoBuilder {
@@ -94,6 +96,7 @@ impl MockGitInfoBuilder {
9496
milestones: Vec::new(),
9597
users: Vec::new(),
9698
dirty_files: Vec::new(),
99+
git_status: GitStatus::Clean,
97100
}
98101
}
99102

@@ -142,6 +145,11 @@ impl MockGitInfoBuilder {
142145
self
143146
}
144147

148+
pub fn with_status(mut self, status: GitStatus) -> Self {
149+
self.git_status = status;
150+
self
151+
}
152+
145153
pub fn build(self) -> MockGitInfo {
146154
MockGitInfo {
147155
owner: self.owner,
@@ -153,6 +161,7 @@ impl MockGitInfoBuilder {
153161
milestones: Arc::new(Mutex::new(self.milestones)),
154162
users: Arc::new(Mutex::new(self.users)),
155163
dirty_files: Arc::new(Mutex::new(self.dirty_files)),
164+
git_status: self.git_status,
156165
calls: Arc::new(Mutex::new(Vec::new())),
157166
write_calls: Arc::new(Mutex::new(Vec::new())),
158167
}
@@ -218,7 +227,7 @@ impl GitHelpers for MockGitInfo {
218227

219228
impl GitStatusOps for MockGitInfo {
220229
fn status(&self) -> Result<GitStatus, GitStatusError> {
221-
Ok(GitStatus::Clean)
230+
Ok(self.git_status.clone())
222231
}
223232

224233
fn dirty(&self) -> Result<Vec<PathBuf>, GitStatusError> {

0 commit comments

Comments
 (0)