Files
gitks/pack/advertise_refs.rs
T
zhenyi ab32e8826e feat(pack): add raw advertise refs and stateless protocol support
- Add raw flag to AdvertiseRefsRequest to enable raw pkt-line output
- Implement advertise_refs_raw function that calls git upload-pack/receive-pack with --advertise-refs
- Add stateless flag to GitProtocolFeatures for HTTP smart protocol support
- Modify upload_pack and receive_pack to accept stateless parameter
- Update command construction to include --stateless-rpc flag when enabled
- Add raw_data field to AdvertiseRefsResponse for raw output
- Update pack cache key computation to include raw service differentiation
- Initialize raw field to false in all request creation calls
2026-06-08 21:46:31 +08:00

115 lines
3.7 KiB
Rust

use crate::bare::GitBare;
use crate::error::{GitError, GitResult};
use crate::pb::{AdvertiseRefsRequest, AdvertiseRefsResponse, ReferenceAdvertisement};
impl GitBare {
pub fn advertise_refs(
&self,
request: AdvertiseRefsRequest,
) -> GitResult<AdvertiseRefsResponse> {
if request.raw {
return self.advertise_refs_raw(&request);
}
let repo = self.gix_repo()?;
let mut references = Vec::new();
for r in repo.references()?.all()? {
let mut r = match r {
Ok(r) => r,
Err(_) => continue,
};
let name = r.name().to_string();
let target_oid = r.peel_to_id().ok().map(|id| self.oid_to_pb(id.to_string()));
let is_symbolic = r.target().try_id().is_none();
let symbolic_target = if is_symbolic {
match r.target() {
gix::refs::TargetRef::Symbolic(name) => name.to_string(),
_ => String::new(),
}
} else {
String::new()
};
// Peel past tags to get the commit OID if this is a tag ref
let peeled_oid = if name.starts_with("refs/tags/") {
r.peel_to_id().ok().map(|id| self.oid_to_pb(id.to_string()))
} else {
None
};
references.push(ReferenceAdvertisement {
name,
target_oid,
peeled_oid,
symbolic: is_symbolic,
symbolic_target,
});
}
// Sort by name for deterministic output
references.sort_by(|a, b| a.name.cmp(&b.name));
Ok(AdvertiseRefsResponse {
references,
capabilities: vec![
"report-status".into(),
"delete-refs".into(),
"side-band-64k".into(),
"ofs-delta".into(),
"multi_ack_detailed".into(),
"multi_ack".into(),
"symref=HEAD".into(),
],
raw_data: Vec::new(),
})
}
/// Return raw pkt-line output from git upload-pack/receive-pack --advertise-refs.
/// Used by transparent proxies (gitshell) that forward bytes verbatim to git clients.
fn advertise_refs_raw(
&self,
request: &AdvertiseRefsRequest,
) -> GitResult<AdvertiseRefsResponse> {
let bare_dir_str = self.bare_dir.to_string_lossy().into_owned();
let stateless = request.protocol.as_ref().is_some_and(|p| p.stateless);
// Default to upload-pack if service is unspecified
let subcommand = if request.service == "git-receive-pack" {
"receive-pack"
} else {
"upload-pack"
};
let mut args: Vec<String> = vec![
"--git-dir".into(),
bare_dir_str,
subcommand.into(),
"--advertise-refs".into(),
];
if stateless {
args.push("--stateless-rpc".into());
}
let result = duct::cmd("git", &args)
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;
if !result.status.success() {
return Err(GitError::CommandFailed {
status_code: result.status.code(),
stderr: String::from_utf8_lossy(&result.stderr).into_owned(),
});
}
tracing::debug!(
raw_len = result.stdout.len(),
service = %request.service,
stateless = stateless,
"advertise_refs raw output"
);
Ok(AdvertiseRefsResponse {
references: Vec::new(),
capabilities: Vec::new(),
raw_data: result.stdout,
})
}
}