Skip to content
Clone

fix(git): answer missing tags with 404, keep ref names short, resolve nested submodules

Four gaps the API audit surfaced by probing the live server:

- `get_tag` went through KenShell's `GetTag`, which fails the call (not returns
  empty) when a tag is absent, so every unknown tag answered 500 and the UI
  reported a gateway error instead of "tag deleted". Resolve through
  `list_tags` and match the name, which keeps a missing tag a plain 404.
- `GET /branches/{name}` returned `refs/heads/main` while the list returned
  `main`; feeding either back into a path only worked for the list's shape.
  `BranchDto::from_info` now strips the prefix for both.
- `submodule_resolve` only looked at the root tree, so a nested submodule
  (`vendor/lib`, legal in `.gitmodules`) 404'd. It now reads the parent
  directory and matches the basename.
- `list_pins` declared `AppResponseData<Page<MessageDto>>` in its utoipa block
  while returning a `Vec`; the generated client advertised a page and the
  frontend read `.items` off an array. The annotation now matches the handler.
ZhenYi committed
parent 7f0e1a0 commit 78f6249 Browse files

4 files changed

+25 −5

File tree 4
Modified lib/service/chat/src/handlers/messages.rs View lib/service/chat/src/handlers/messages.rs
@@ -187,7 +187,7 @@
187187 description = "返回房间内当前置顶且未删除的消息列表。",
188188 params(("room_id" = Uuid, Path, description = "room id")),
189189 responses(
190 (status = 200, description = "pinned messages", body = AppResponseData<api::pagination::Page<MessageDto>>),
190 (status = 200, description = "pinned messages", body = AppResponseData<Vec<MessageDto>>),
191191 (status = 404, description = "not found", body = AppResponseData<NoData>),
192192 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
193193 )
Modified lib/service/git/src/service/branches.rs View lib/service/git/src/service/branches.rs
@@ -28,7 +28,9 @@
2828 fn from_info(info: &BranchInfo, is_protected: bool) -> Self {
2929 let now = OffsetDateTime::now_utc();
3030 Self {
31 name: info.name.clone(),
31 // `list` answers short names while `find` answers the full ref; keep
32 // one shape so a name read from either place can be fed back in.
33 name: info.name.trim_start_matches("refs/heads/").to_string(),
3234 commit_sha: info.sha.to_string(),
3335 is_protected,
3436 is_deleted: info.is_deleted,
Modified lib/service/git/src/service/submodules.rs View lib/service/git/src/service/submodules.rs
@@ -67,14 +67,21 @@
6767 "repo.{}.submodule_resolve.{}.{}",
6868 ctx.model.id, rev_oid, path
6969 );
70 // `.gitmodules` allows nested paths (`vendor/lib`), so list the parent
71 // directory and match the basename there instead of only the root tree.
72 let normalized = path.trim_matches('/');
73 let (dir, leaf) = match normalized.rsplit_once('/') {
74 Some((dir, leaf)) => (dir.to_string(), leaf.to_string()),
75 None => (String::new(), normalized.to_string()),
76 };
7077 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
7178 let entries = baby
72 .get_tree_entries(&revision, std::path::Path::new(""))
79 .get_tree_entries(&revision, std::path::Path::new(&dir))
7380 .await
7481 .map_err(map_baby)?;
7582 entries
7683 .into_iter()
77 .find(|e| e.name == path && e.mode == TreeEntryMode::Submodule)
84 .find(|e| e.name == leaf && e.mode == TreeEntryMode::Submodule)
7885 .map(|e| e.oid.to_string())
7986 .ok_or(GitError::NotFound)
8087 })
Modified lib/service/git/src/service/tags.rs View lib/service/git/src/service/tags.rs
@@ -101,8 +101,19 @@
101101 if name.trim().is_empty() || name.contains(['\0', '\n', '\r', ' ']) {
102102 return Err(GitError::InvalidArgument("invalid tag name".to_string()));
103103 }
104 // KenShell's GetTag fails the call for a missing tag, which surfaced as a
105 // 500; resolving through the listing keeps a deleted tag a plain 404.
104106 let baby = self.baby(&ctx);
105 let info = baby.find_tag(&name).await.map_err(map_baby)?;
107 let tags = baby
108 .list_tags(ListTagsOptions {
109 patterns: vec!["refs/tags/*".to_string()],
110 })
111 .await
112 .map_err(map_baby)?;
113 let info = tags
114 .into_iter()
115 .find(|tag| tag.name.trim_start_matches("refs/tags/") == name)
116 .ok_or(GitError::NotFound)?;
106117 Ok(dto_from_info(&info))
107118 }
108119