use crate::auth::SecureString;
use crate::cli::UI;
use crate::graphrag;
use crate::ops::fetch;
use crate::ops::oplog;
use anyhow::{bail, Result};
use std::path::Path;
pub struct PullOptions<'a> {
pub remote_name: &'a str,
pub tags: bool,
pub rebase: bool,
pub ff_only: bool,
pub token: Option<&'a SecureString>,
pub ssh_key: Option<&'a std::path::Path>,
}
pub fn execute(path: &Path, opts: &PullOptions, ui: &UI) -> Result<()> {
oplog::with_oplog(
path,
"pull",
&format!("pull from '{}'", opts.remote_name),
|| execute_inner(path, opts, ui),
)
}
fn execute_inner(path: &Path, opts: &PullOptions, ui: &UI) -> Result<()> {
fetch::execute(
path,
opts.remote_name,
opts.tags,
opts.token,
opts.ssh_key,
ui,
)?;
let (branch_name, remote_url, action) = {
let repo = crate::ops::open_repo(path)?;
let branch_name = {
let head = repo.head()?;
head.shorthand().unwrap_or("main").to_string()
};
let remote_url = repo
.find_remote(opts.remote_name)
.ok()
.and_then(|r| r.url().map(|s| s.to_string()));
let remote_branch = format!("{}/{}", opts.remote_name, branch_name);
let action = match repo.find_branch(&remote_branch, git2::BranchType::Remote) {
Ok(reference) => {
let annotated = repo.reference_to_annotated_commit(reference.get())?;
let (analysis, _) = repo.merge_analysis(&[&annotated])?;
if analysis.is_up_to_date() {
PullAction::UpToDate
} else if analysis.is_fast_forward() {
let target_commit = repo.find_commit(annotated.id())?;
let mut head_ref = repo.head()?;
head_ref.set_target(annotated.id(), "pull: fast-forward")?;
repo.checkout_tree(target_commit.as_object(), None)?;
PullAction::FastForward
} else if opts.ff_only {
PullAction::FfOnlyFailed
} else if opts.rebase {
PullAction::Rebase
} else if analysis.is_normal() {
repo.merge(&[&annotated], None, None)?;
let index = repo.index()?;
if index.has_conflicts() {
PullAction::MergeConflict
} else {
let mut index = repo.index()?;
let tree_oid = index.write_tree()?;
let tree = repo.find_tree(tree_oid)?;
let sig = repo.signature()?;
let head_commit = repo.head()?.peel_to_commit()?;
let merge_commit = repo.find_commit(annotated.id())?;
let msg = format!("Merge branch '{}' of {}", branch_name, opts.remote_name);
repo.commit(
Some("HEAD"),
&sig,
&sig,
&msg,
&tree,
&[&head_commit, &merge_commit],
)?;
repo.cleanup_state()?;
PullAction::Merged
}
} else {
PullAction::UpToDate
}
}
Err(_) => PullAction::NoUpstream,
};
(branch_name, remote_url, action)
};
let changed = match action {
PullAction::UpToDate => {
ui.success("Already up to date");
false
}
PullAction::FastForward => {
ui.success("Fast-forward");
true
}
PullAction::FfOnlyFailed => {
bail!(
"Not possible to fast-forward, aborting.\n\
Use 'securegit pull' without --ff-only, or 'securegit pull --rebase'."
);
}
PullAction::Rebase => {
let upstream_ref = format!("{}/{}", opts.remote_name, branch_name);
crate::ops::rebase::execute(path, Some(&upstream_ref), false, false, false, None, ui)?;
true
}
PullAction::MergeConflict => {
ui.warning("Merge conflict detected. Resolve conflicts and commit.");
true
}
PullAction::Merged => {
ui.success("Merge made");
true
}
PullAction::NoUpstream => {
ui.warning(format!("No upstream branch found for '{}'", branch_name));
false
}
};
if changed {
graphrag::client::trigger_indexing_sync(path, remote_url.as_deref(), Some(&branch_name));
}
Ok(())
}
enum PullAction {
UpToDate,
FastForward,
FfOnlyFailed,
Rebase,
MergeConflict,
Merged,
NoUpstream,
}