use crate::fetch::Progress;
use crate::objects::ObjectId;
use crate::progress::Cancel;
use crate::remote::{HttpClientFactory, Remote};
use crate::repo::Repository;
use crate::transfer::{
FetchOptions, PushOptions, PushOutcome, PushRefSpec, TagMode, TransferStats,
};
use super::{
in_ref, out_ref, same_branch_name, snapshot_fetch_refspec, upstream_ref, valid_branch_name,
BranchInfo, CheckoutError, CheckoutInfo, CheckoutMarker, Result, SnapshotRoot,
};
impl CheckoutMarker {
pub fn remote(&self) -> Result<Remote> {
let mut remote = Remote::from_url(&self.url)?;
remote.fetch_refspecs = vec![snapshot_fetch_refspec(&self.id, &self.branch)];
Ok(remote)
}
}
pub fn push_for_checkin(
repo: &Repository,
marker: &CheckoutMarker,
branches: &[(String, ObjectId)],
progress: &mut dyn Progress,
cancel: &dyn Cancel,
http_factory: Option<&dyn HttpClientFactory>,
) -> Result<PushOutcome> {
let specs: Vec<PushRefSpec> = branches
.iter()
.map(|(name, tip)| PushRefSpec {
src: Some(*tip),
src_ref: Some(format!("refs/heads/{name}")),
dst: out_ref(&marker.id, name),
force: true,
..Default::default()
})
.collect();
Ok(marker.remote()?.push(
repo,
&specs,
PushOptions::default(),
progress,
cancel,
http_factory,
)?)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FetchedSnapshot {
pub marker: CheckoutMarker,
pub previous: Option<SnapshotRoot>,
pub current: SnapshotRoot,
pub transfer: TransferStats,
}
impl FetchedSnapshot {
#[must_use]
pub fn moved(&self) -> bool {
self.previous
.as_ref()
.is_none_or(|p| p.commit != self.current.commit)
}
}
pub fn fetch_snapshot(
repo: &Repository,
marker: &CheckoutMarker,
info: &CheckoutInfo,
fetch: FetchOptions,
progress: &mut dyn Progress,
cancel: &dyn Cancel,
http_factory: Option<&dyn HttpClientFactory>,
) -> Result<FetchedSnapshot> {
let root = ObjectId::from_hex(&info.root).map_err(|e| CheckoutError::Protocol {
url: marker.url.clone(),
detail: format!("the snapshot {:?} is not a commit id: {e}", info.root),
})?;
let upstream = upstream_ref(&info.branch);
let local = || crate::refs::resolve_ref(repo.git_dir(), &upstream).ok();
let mut transfer = TransferStats::default();
if local() != Some(root) || !repo.odb().exists(&root) {
let options = FetchOptions {
refspecs: vec![snapshot_fetch_refspec(&info.id, &info.branch)],
tags: TagMode::None,
prune: false,
dry_run: false,
remote_name: None,
..fetch
};
transfer = marker
.remote()?
.fetch(repo, options, progress, cancel, http_factory)?
.transfer;
let got = local();
if got != Some(root) {
return Err(CheckoutError::SnapshotMoved {
expected: info.root.clone(),
got: got
.map(|g| g.to_hex())
.unwrap_or_else(|| "nothing".to_owned()),
});
}
}
let updated = CheckoutMarker {
roots: info.roots.clone(),
..marker.clone()
};
updated.write(repo.git_dir())?;
Ok(FetchedSnapshot {
previous: marker.last().cloned(),
current: SnapshotRoot {
commit: info.root.clone(),
base: info.base.clone(),
},
marker: updated,
transfer,
})
}
pub fn fetch_branch(
repo: &Repository,
marker: &CheckoutMarker,
info: &BranchInfo,
fetch: FetchOptions,
progress: &mut dyn Progress,
cancel: &dyn Cancel,
http_factory: Option<&dyn HttpClientFactory>,
) -> Result<TransferStats> {
let protocol = |detail: String| CheckoutError::Protocol {
url: marker.url.clone(),
detail,
};
if !valid_branch_name(&info.name) || same_branch_name(&info.name, &marker.branch) {
return Err(protocol(format!(
"the server answered with branch {:?}, which this checkout can't take",
info.name
)));
}
let projected = ObjectId::from_hex(&info.projected).map_err(|e| {
protocol(format!(
"the projection {:?} is not a commit id: {e}",
info.projected
))
})?;
let upstream = upstream_ref(&info.name);
let local = || crate::refs::resolve_ref(repo.git_dir(), &upstream).ok();
if local() == Some(projected) && repo.odb().exists(&projected) {
return Ok(TransferStats::default());
}
let options = FetchOptions {
refspecs: vec![format!("+{}:{upstream}", in_ref(&marker.id, &info.name))],
tags: TagMode::None,
prune: false,
dry_run: false,
remote_name: None,
..fetch
};
let transfer = marker
.remote()?
.fetch(repo, options, progress, cancel, http_factory)?
.transfer;
let got = local();
if got != Some(projected) {
return Err(CheckoutError::BranchMoved {
name: info.name.clone(),
expected: info.projected.clone(),
got: got
.map(|g| g.to_hex())
.unwrap_or_else(|| "nothing".to_owned()),
});
}
Ok(transfer)
}