mirror of
https://github.com/qwjyh/xdbm
synced 2025-04-20 11:45:50 +09:00
fix(sync): add pull (only fast forward)
This commit is contained in:
parent
9316290d28
commit
9bf534df1d
1 changed files with 194 additions and 41 deletions
205
src/cmd_sync.rs
205
src/cmd_sync.rs
|
@ -1,7 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
|
use git2::{build::CheckoutBuilder, Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
|
||||||
|
|
||||||
pub(crate) fn cmd_sync(
|
pub(crate) fn cmd_sync(
|
||||||
config_dir: &PathBuf,
|
config_dir: &PathBuf,
|
||||||
|
@ -21,12 +24,154 @@ pub(crate) fn cmd_sync(
|
||||||
remotes.get(0).unwrap().to_string()
|
remotes.get(0).unwrap().to_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
debug!("remote name: {remote_name}");
|
||||||
|
|
||||||
|
let mut remote = repo.find_remote(&remote_name)?;
|
||||||
|
let callbacks = remote_callback(&use_sshagent, &ssh_key);
|
||||||
|
let mut fetchoptions = FetchOptions::new();
|
||||||
|
fetchoptions.remote_callbacks(callbacks);
|
||||||
|
let fetch_refspec: Vec<String> = remote
|
||||||
|
.refspecs()
|
||||||
|
.filter_map(|rs| match rs.direction() {
|
||||||
|
git2::Direction::Fetch => rs.str().map(|s| s.to_string()),
|
||||||
|
git2::Direction::Push => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
remote
|
||||||
|
.fetch(&fetch_refspec, Some(&mut fetchoptions), None)
|
||||||
|
.context("Failed to fetch (pull)")?;
|
||||||
|
let stats = remote.stats();
|
||||||
|
if stats.local_objects() > 0 {
|
||||||
|
println!(
|
||||||
|
"\rReceived {}/{} objects in {} bytes (used {} local objects)",
|
||||||
|
stats.indexed_objects(),
|
||||||
|
stats.total_objects(),
|
||||||
|
stats.received_bytes(),
|
||||||
|
stats.local_objects(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"\rReceived {}/{} objects in {} bytes",
|
||||||
|
stats.indexed_objects(),
|
||||||
|
stats.total_objects(),
|
||||||
|
stats.received_bytes(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let fetch_head = repo
|
||||||
|
.reference_to_annotated_commit(
|
||||||
|
&repo
|
||||||
|
.resolve_reference_from_short_name(&remote_name)
|
||||||
|
.context("failed to get reference from fetch refspec")?,
|
||||||
|
)
|
||||||
|
.context("failed to get annotated commit")?;
|
||||||
|
let (merge_analysis, merge_preference) = repo
|
||||||
|
.merge_analysis(&[&fetch_head])
|
||||||
|
.context("failed to do merge_analysis")?;
|
||||||
|
|
||||||
|
trace!("merge analysis: {:?}", merge_analysis);
|
||||||
|
trace!("merge preference: {:?}", merge_preference);
|
||||||
|
match merge_analysis {
|
||||||
|
ma if ma.is_up_to_date() => {
|
||||||
|
info!("HEAD is up to date. skip merging");
|
||||||
|
}
|
||||||
|
ma if ma.is_fast_forward() => {
|
||||||
|
// https://github.com/rust-lang/git2-rs/blob/master/examples/pull.rs
|
||||||
|
info!("fast forward is available");
|
||||||
|
let mut ref_remote = repo
|
||||||
|
.find_reference(
|
||||||
|
remote
|
||||||
|
.default_branch()
|
||||||
|
.context("failed to get remote default branch")?
|
||||||
|
.as_str()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.context("failed to get remote reference")?;
|
||||||
|
let name = match ref_remote.name() {
|
||||||
|
Some(s) => s.to_string(),
|
||||||
|
None => String::from_utf8_lossy(ref_remote.name_bytes()).to_string(),
|
||||||
|
};
|
||||||
|
let msg = format!("Fast-Forward: Setting {} to id: {}", name, fetch_head.id());
|
||||||
|
println!("{}", msg);
|
||||||
|
ref_remote
|
||||||
|
.set_target(fetch_head.id(), &msg)
|
||||||
|
.context("failed to set target")?;
|
||||||
|
repo.checkout_head(Some(CheckoutBuilder::default().force()))
|
||||||
|
.context("failed to checkout")?;
|
||||||
|
}
|
||||||
|
ma if ma.is_unborn() => {
|
||||||
|
warn!("HEAD is invalid (unborn)");
|
||||||
|
return Err(anyhow!(
|
||||||
|
"HEAD is invalid: merge_analysis: {:?}",
|
||||||
|
merge_analysis
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ma if ma.is_normal() => {
|
||||||
|
error!("unable to fast-forward. manual merge is required");
|
||||||
|
return Err(anyhow!("unable to fast-forward. manual merge is required"));
|
||||||
|
}
|
||||||
|
ma if ma.is_none() => {
|
||||||
|
error!("no merge is possible");
|
||||||
|
return Err(anyhow!("no merge is possible"));
|
||||||
|
}
|
||||||
|
_ma => {
|
||||||
|
error!(
|
||||||
|
"this code must not reachable: merge_analysis {:?}",
|
||||||
|
merge_analysis
|
||||||
|
);
|
||||||
|
return Err(anyhow!("must not be reachabel (uncovered merge_analysis)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// push
|
||||||
|
let callbacks = remote_callback(&use_sshagent, &ssh_key);
|
||||||
|
let mut push_options = PushOptions::new();
|
||||||
|
push_options.remote_callbacks(callbacks);
|
||||||
|
trace!("remote: {:?}", remote.name());
|
||||||
|
let num_refspecs = remote
|
||||||
|
.refspecs()
|
||||||
|
.filter(|rs| rs.direction() == git2::Direction::Push)
|
||||||
|
.count();
|
||||||
|
if num_refspecs > 1 {
|
||||||
|
warn!("more than one push refspecs are configured");
|
||||||
|
warn!("using the first one");
|
||||||
|
}
|
||||||
|
let head = repo.head().context("Failed to get HEAD")?;
|
||||||
|
if num_refspecs >= 1 {
|
||||||
|
trace!("using push refspec");
|
||||||
|
let push_refspec = remote
|
||||||
|
.refspecs()
|
||||||
|
.filter_map(|rs| match rs.direction() {
|
||||||
|
git2::Direction::Fetch => None,
|
||||||
|
git2::Direction::Push => Some(rs),
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.expect("this must be unreachabe")
|
||||||
|
.str()
|
||||||
|
.context("failed to get valid utf8 push refspec")?
|
||||||
|
.to_string();
|
||||||
|
remote.push(&[push_refspec.as_str()] as &[&str], Some(&mut push_options))?;
|
||||||
|
} else {
|
||||||
|
trace!("using head as push refspec");
|
||||||
|
trace!("head is branch: {:?}", head.is_branch());
|
||||||
|
trace!("head is remote: {:?}", head.is_remote());
|
||||||
|
let push_refspec = head.name().context("failed to get head name")?;
|
||||||
|
remote.push(&[push_refspec] as &[&str], Some(&mut push_options))?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_callback<'b, 'a>(
|
||||||
|
use_sshagent: &'a bool,
|
||||||
|
ssh_key: &'b Option<PathBuf>,
|
||||||
|
) -> RemoteCallbacks<'a>
|
||||||
|
where
|
||||||
|
'b: 'a,
|
||||||
|
{
|
||||||
// using credentials
|
// using credentials
|
||||||
let mut callbacks = RemoteCallbacks::new();
|
let mut callbacks = RemoteCallbacks::new();
|
||||||
callbacks
|
callbacks
|
||||||
.credentials(|_url, username_from_url, _allowed_types| {
|
.credentials(move |_url, username_from_url, _allowed_types| {
|
||||||
if let Some(key) = &ssh_key {
|
if let Some(ref key) = ssh_key {
|
||||||
info!("Using provided ssh key to access the repository");
|
info!("Using provided ssh key to access the repository");
|
||||||
let passwd = match inquire::Password::new("SSH passphrase").prompt() {
|
let passwd = match inquire::Password::new("SSH passphrase").prompt() {
|
||||||
std::result::Result::Ok(s) => Some(s),
|
std::result::Result::Ok(s) => Some(s),
|
||||||
|
@ -42,7 +187,7 @@ pub(crate) fn cmd_sync(
|
||||||
key as &Path,
|
key as &Path,
|
||||||
passwd.as_deref(),
|
passwd.as_deref(),
|
||||||
)
|
)
|
||||||
} else if use_sshagent {
|
} else if *use_sshagent {
|
||||||
// use ssh agent
|
// use ssh agent
|
||||||
info!("Using ssh agent to access the repository");
|
info!("Using ssh agent to access the repository");
|
||||||
Cred::ssh_key_from_agent(
|
Cred::ssh_key_from_agent(
|
||||||
|
@ -54,10 +199,34 @@ pub(crate) fn cmd_sync(
|
||||||
panic!("This option must be unreachable.")
|
panic!("This option must be unreachable.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.transfer_progress(|progress| {
|
||||||
|
if progress.received_objects() == progress.total_objects() {
|
||||||
|
print!(
|
||||||
|
"Resolving deltas {}/{}\r",
|
||||||
|
progress.indexed_deltas(),
|
||||||
|
progress.total_deltas()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print!(
|
||||||
|
"Received {}/{} objects ({}) in {} bytes\r",
|
||||||
|
progress.received_objects(),
|
||||||
|
progress.total_objects(),
|
||||||
|
progress.indexed_objects(),
|
||||||
|
progress.received_bytes(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.sideband_progress(|text| {
|
||||||
|
let msg = String::from_utf8_lossy(text);
|
||||||
|
eprintln!("remote: {msg}");
|
||||||
|
true
|
||||||
|
})
|
||||||
.push_transfer_progress(|current, total, bytes| {
|
.push_transfer_progress(|current, total, bytes| {
|
||||||
trace!("{current},\t{total},\t{bytes}");
|
trace!("{current}/{total} files sent \t{bytes} bytes");
|
||||||
});
|
})
|
||||||
callbacks.push_update_reference(|reference_name, status_msg| {
|
.push_update_reference(|reference_name, status_msg| {
|
||||||
debug!("remote reference_name {reference_name}");
|
debug!("remote reference_name {reference_name}");
|
||||||
match status_msg {
|
match status_msg {
|
||||||
None => {
|
None => {
|
||||||
|
@ -74,21 +243,5 @@ pub(crate) fn cmd_sync(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut push_options = PushOptions::new();
|
callbacks
|
||||||
push_options.remote_callbacks(callbacks);
|
|
||||||
let mut remote = repo.find_remote(&remote_name)?;
|
|
||||||
trace!("remote: {:?}", remote.name());
|
|
||||||
if remote.refspecs().len() != 1 {
|
|
||||||
warn!("multiple refspecs found");
|
|
||||||
}
|
|
||||||
trace!("refspec: {:?}", remote.get_refspec(0).unwrap().str());
|
|
||||||
trace!("refspec: {:?}", remote.get_refspec(0).unwrap().direction());
|
|
||||||
trace!("refspec: {:?}", repo.head().unwrap().name());
|
|
||||||
trace!("head is branch: {:?}", repo.head().unwrap().is_branch());
|
|
||||||
trace!("head is remote: {:?}", repo.head().unwrap().is_remote());
|
|
||||||
remote.push(
|
|
||||||
&[repo.head().unwrap().name().unwrap()] as &[&str],
|
|
||||||
Some(&mut push_options),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue