use crate::auth;
use crate::auth::SecureString;
use crate::cli::UI;
use crate::graphrag;
use anyhow::{bail, Result};
use std::path::Path;
pub struct PushOptions<'a> {
pub set_upstream: bool,
pub force: bool,
pub tags: bool,
pub all: bool,
pub token: Option<&'a SecureString>,
pub ssh_key: Option<&'a Path>,
}
pub fn execute(
path: &Path,
remote_name: &str,
branch: Option<&str>,
opts: PushOptions,
ui: &UI,
) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let prefix = if opts.force { "+" } else { "" };
let mut refspecs: Vec<String> = Vec::new();
let mut resolved_branch: Option<String> = None;
if opts.all {
let branches = repo.branches(Some(git2::BranchType::Local))?;
for branch_result in branches {
let (b, _) = branch_result?;
if let Some(name) = b.name()? {
refspecs.push(format!("{}refs/heads/{}:refs/heads/{}", prefix, name, name));
}
}
if let Ok(head) = repo.head() {
resolved_branch = head.shorthand().map(|s| s.to_string());
}
} else if !opts.tags {
let ref_name = if let Some(b) = branch {
b.to_string()
} else {
if repo.head_detached()? {
bail!(
"You are not currently on any branch.\n\
To push, specify the branch name: securegit push <remote> <branch>"
);
}
let head = repo.head()?;
head.shorthand()
.ok_or_else(|| anyhow::anyhow!("Could not determine current branch name"))?
.to_string()
};
let is_tag = repo
.find_branch(&ref_name, git2::BranchType::Local)
.is_err()
&& repo
.find_reference(&format!("refs/tags/{}", ref_name))
.is_ok();
if is_tag {
refspecs.push(format!(
"{}refs/tags/{}:refs/tags/{}",
prefix, ref_name, ref_name
));
} else {
refspecs.push(format!(
"{}refs/heads/{}:refs/heads/{}",
prefix, ref_name, ref_name
));
resolved_branch = Some(ref_name);
}
} else if let Some(b) = branch {
let branch_name = b.to_string();
refspecs.push(format!(
"{}refs/heads/{}:refs/heads/{}",
prefix, branch_name, branch_name
));
resolved_branch = Some(branch_name);
}
if opts.tags {
let tag_names = repo.tag_names(None)?;
for tag in tag_names.iter().flatten() {
refspecs.push(format!("{}refs/tags/{}:refs/tags/{}", prefix, tag, tag));
}
}
if refspecs.is_empty() {
bail!("Nothing to push.");
}
let mut remote = repo.find_remote(remote_name)?;
let host = remote
.url()
.and_then(|u| url::Url::parse(u).ok())
.and_then(|u| u.host_str().map(|h| h.to_string()));
let mut callbacks = auth::build_git2_callbacks(opts.token, opts.ssh_key, host);
callbacks.push_transfer_progress(|_current, _total, _bytes| {});
let mut push_opts = git2::PushOptions::new();
push_opts.remote_callbacks(callbacks);
let refspec_strs: Vec<&str> = refspecs.iter().map(|s| s.as_str()).collect();
remote.push(&refspec_strs, Some(&mut push_opts))?;
let remote_url = remote.url().map(|s| s.to_string());
ui.info(format!("To {}", remote_url.as_deref().unwrap_or("")));
if let Some(ref branch_name) = resolved_branch {
ui.field("Branch", format!("{} -> {}", branch_name, branch_name));
}
if opts.tags {
ui.info("[tags pushed]");
}
if opts.all {
ui.info("[all branches pushed]");
}
if opts.set_upstream {
if let Some(ref branch_name) = resolved_branch {
let mut local_branch = repo.find_branch(branch_name, git2::BranchType::Local)?;
let upstream_name = format!("{}/{}", remote_name, branch_name);
local_branch.set_upstream(Some(&upstream_name))?;
ui.success(format!(
"Branch '{}' set up to track '{}' from '{}'.",
branch_name, branch_name, remote_name
));
}
}
let branch_for_graphrag = resolved_branch.as_deref();
graphrag::client::trigger_indexing_sync(path, remote_url.as_deref(), branch_for_graphrag);
crate::ops::backup::trigger_auto_backup(path);
Ok(())
}