Compare commits

...

4 Commits

Author SHA1 Message Date
ZhenYi
ee4ff6c752 fix(api): fix branch route order to prevent shadowing
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
Move all specific branch routes before /branches/{name} to prevent
route shadowing. Previously, routes like /branches/rename, /branches/move,
/branches/upstream, /branches/diff, etc. were shadowed by /branches/{name}.
2026-04-17 16:11:23 +08:00
ZhenYi
1272615d50 fix(api): fix refs route order to prevent shadowing
Move specific routes (/refs/rename, /refs/update) before parameterized
routes (/refs/{name}) to avoid route shadowing.
2026-04-17 16:10:23 +08:00
ZhenYi
4cee9975d5 fix(api): fix commit route order to prevent InvalidOid("reflog") error
Move specific routes (/commits/reflog, /commits/branches, /commits/tags)
before parameterized routes (/commits/{oid}) to avoid route shadowing.
Previously, /commits/reflog was matched by /commits/{oid} with oid="reflog",
causing InvalidOid("reflog") errors.

Also fixes other potential route shadowing issues in commit routes.
2026-04-17 16:07:01 +08:00
ZhenYi
82ed726848 fix(git): convert default_branch to full ref name (refs/heads/*)
The database stores short branch names (e.g., "main"), but git2's push_ref()
requires full reference names (e.g., "refs/heads/main"). This fixes all
service-layer endpoints to convert default_branch to the full ref format.

Fixed endpoints:
- git_readme: convert to refs/heads/{branch}
- git_commit_count: convert to refs/heads/{branch}
- git_contributors: convert to refs/heads/{branch}
- git_commit_log: convert to refs/heads/{branch}
- git_commit_walk: convert to refs/heads/{branch}

Resolves errors:
- Internal("the given reference name 'main' is not valid")
2026-04-17 15:56:08 +08:00
4 changed files with 69 additions and 39 deletions

View File

@ -54,39 +54,17 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
.route("/blob/{oid}/content", web::get().to(blob::git_blob_content))
.route("/blob/{oid}/size", web::get().to(blob::git_blob_size))
.route("/readme", web::get().to(blob::git_readme))
// branch
// branch - specific routes first, then parameterized routes
.route("/branches", web::get().to(branch::git_branch_list))
.route(
"/branches/summary",
web::get().to(branch::git_branch_summary),
)
.route("/branches", web::post().to(branch::git_branch_create))
// NOTE: /branches/current MUST be before /branches/{name} to avoid being shadowed
.route(
"/branches/current",
web::get().to(branch::git_branch_current),
)
.route("/branches/{name}", web::get().to(branch::git_branch_get))
.route(
"/branches/{name}",
web::delete().to(branch::git_branch_delete),
)
.route(
"/branches/{name}/exists",
web::get().to(branch::git_branch_exists),
)
.route(
"/branches/{name}/is-head",
web::get().to(branch::git_branch_is_head),
)
.route(
"/branches/{name}/upstream",
web::get().to(branch::git_branch_upstream),
)
.route(
"/branches/{name}/tracking-difference",
web::get().to(branch::git_branch_tracking_difference),
)
.route(
"/branches/remote/{name}",
web::delete().to(branch::git_branch_delete_remote),
@ -125,7 +103,29 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
"/branches/is-conflicted",
web::get().to(branch::git_branch_is_conflicted),
)
// commit
// parameterized routes with {name}
.route("/branches/{name}", web::get().to(branch::git_branch_get))
.route(
"/branches/{name}",
web::delete().to(branch::git_branch_delete),
)
.route(
"/branches/{name}/exists",
web::get().to(branch::git_branch_exists),
)
.route(
"/branches/{name}/is-head",
web::get().to(branch::git_branch_is_head),
)
.route(
"/branches/{name}/upstream",
web::get().to(branch::git_branch_upstream),
)
.route(
"/branches/{name}/tracking-difference",
web::get().to(branch::git_branch_tracking_difference),
)
// commit - specific routes first, then parameterized routes
.route("/commits", web::get().to(commit::git_commit_log))
.route("/commits/count", web::get().to(commit::git_commit_count))
.route("/commits", web::post().to(commit::git_commit_create))
@ -139,6 +139,13 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
"/commits/resolve/{rev}",
web::get().to(commit::git_commit_resolve_rev),
)
.route("/commits/reflog", web::get().to(commit::git_commit_reflog))
.route(
"/commits/branches",
web::get().to(commit::git_commit_branches),
)
.route("/commits/tags", web::get().to(commit::git_commit_tags))
// parameterized routes with {oid}
.route("/commits/{oid}", web::get().to(commit::git_commit_get))
.route("/commits/{oid}", web::patch().to(commit::git_commit_amend))
.route(
@ -193,11 +200,6 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
"/commits/{oid}/refs",
web::get().to(commit::git_commit_refs),
)
.route(
"/commits/branches",
web::get().to(commit::git_commit_branches),
)
.route("/commits/tags", web::get().to(commit::git_commit_tags))
.route(
"/commits/{oid}/is-tip",
web::get().to(commit::git_commit_is_tip),
@ -206,7 +208,6 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
"/commits/{oid}/ref-count",
web::get().to(commit::git_commit_ref_count),
)
.route("/commits/reflog", web::get().to(commit::git_commit_reflog))
.route(
"/commits/{oid}/ancestors",
web::get().to(commit::git_commit_ancestors),
@ -257,13 +258,14 @@ pub fn init_git_routes(cfg: &mut web::ServiceConfig) {
"/diff/side-by-side",
web::get().to(diff::git_diff_side_by_side),
)
// refs
// refs - specific routes first, then parameterized routes
.route("/refs", web::get().to(refs::git_ref_list))
.route("/refs", web::post().to(refs::git_ref_create))
.route("/refs/{name}", web::get().to(refs::git_ref_get))
.route("/refs/{name}", web::delete().to(refs::git_ref_delete))
.route("/refs/rename", web::patch().to(refs::git_ref_rename))
.route("/refs/update", web::patch().to(refs::git_ref_update))
// parameterized routes with {name}
.route("/refs/{name}", web::get().to(refs::git_ref_get))
.route("/refs/{name}", web::delete().to(refs::git_ref_delete))
.route("/refs/{name}/exists", web::get().to(refs::git_ref_exists))
.route("/refs/{name}/target", web::get().to(refs::git_ref_target))
// repo (description, config, merge)

View File

@ -133,7 +133,13 @@ impl AppService {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let rev = query.r#ref.unwrap_or_else(|| repo.default_branch.clone());
let rev = query.r#ref.unwrap_or_else(|| {
if repo.default_branch.is_empty() {
"HEAD".to_string()
} else {
format!("refs/heads/{}", repo.default_branch)
}
});
let tree_oid: git::CommitOid = {
let rev_clone = rev.clone();

View File

@ -786,7 +786,13 @@ impl AppService {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let rev_clone = query.rev.clone().or_else(|| Some(repo.default_branch.clone()));
let rev_clone = query.rev.clone().or_else(|| {
if repo.default_branch.is_empty() {
None
} else {
Some(format!("refs/heads/{}", repo.default_branch))
}
});
let rev_for_count = rev_clone.clone();
let commits = git_spawn!(repo, domain -> {
@ -853,11 +859,15 @@ impl AppService {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let from_clone = from.clone();
let to_clone = to.clone();
let default_branch = repo.default_branch.clone();
let default_ref = if repo.default_branch.is_empty() {
None
} else {
Some(format!("refs/heads/{}", repo.default_branch))
};
let count = git_spawn!(repo, domain -> {
let from_ref = from_clone.as_deref();
let to_ref = to_clone.as_deref().or(Some(default_branch.as_str()));
let to_ref = to_clone.as_deref().or(default_ref.as_deref());
domain.commit_count(from_ref, to_ref)
})?;
@ -1276,7 +1286,13 @@ impl AppService {
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let rev_clone = query.rev.clone();
let rev_clone = query.rev.clone().or_else(|| {
if repo.default_branch.is_empty() {
None
} else {
Some(format!("refs/heads/{}", repo.default_branch))
}
});
let limit = query.limit.unwrap_or(0);
let first_parent_only = query.first_parent_only;
let topological = query.topological;

View File

@ -65,7 +65,13 @@ impl AppService {
}
let repo_clone = repo.clone();
let ref_name_clone = query.ref_name.clone().or_else(|| Some(repo.default_branch.clone()));
let ref_name_clone = query.ref_name.clone().or_else(|| {
if repo.default_branch.is_empty() {
None
} else {
Some(format!("refs/heads/{}", repo.default_branch))
}
});
let commits = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo_clone)?;