use diffler_core::vcs::NetworkOp;
use super::{App, GitOp, Modal, PendingOp, RemotePurpose, fuzzy};
impl App {
pub(crate) fn push(&mut self) {
if self.head.upstream.is_some() {
self.queue_network_op("push", NetworkOp::Push);
} else {
self.push_set_upstream();
}
}
pub(crate) fn push_set_upstream(&mut self) {
if self.declines_jj_network() {
return;
}
let Some(branch) = self.head.branch.clone() else {
self.error("HEAD is detached; nothing to push");
return;
};
let remotes = self.review.vcs.remotes().unwrap_or_default();
match remotes.as_slice() {
[] => self.info("no remote configured"),
[remote] => self.confirm_push_upstream(&branch, remote),
_ => self.open_remote_list(remotes, RemotePurpose::SetUpstreamPush),
}
}
fn confirm_push_upstream(&mut self, branch: &str, remote: &str) {
let op = NetworkOp::PushSetUpstream {
remote: remote.to_owned(),
};
match self.review.vcs.network_argv(op) {
Ok(argv) => {
self.modal = Some(Modal::Confirm {
message: format!("Push {branch} to {remote} and set it as upstream?"),
on_confirm: PendingOp::RunGit {
label: "push -u".into(),
argv,
},
});
}
Err(err) => self.error(err.to_string()),
}
}
pub(crate) fn pull(&mut self) {
if self.declines_jj_network() {
return;
}
if self.head.upstream.is_some() {
self.queue_network_op("pull", NetworkOp::Pull);
return;
}
let remotes = self.review.vcs.remotes().unwrap_or_default();
match remotes.as_slice() {
[] => self.info("no remote configured"),
[remote] => self.pull_from(&remote.clone()),
_ => self.open_remote_list(remotes, RemotePurpose::Pull),
}
}
fn pull_from(&mut self, remote: &str) {
let Some(branch) = self.head.branch.clone() else {
self.error("HEAD is detached; nothing to pull");
return;
};
self.queue_network_op(
"pull",
NetworkOp::PullFrom {
remote: remote.to_owned(),
branch,
},
);
}
fn open_remote_list(&mut self, remotes: Vec<String>, purpose: RemotePurpose) {
let mut list = fuzzy::FuzzyList::default();
list.rerank(&remotes);
self.modal = Some(Modal::RemoteList {
remotes,
list,
purpose,
});
}
pub(super) fn remote_chosen(&mut self, remote: &str, purpose: RemotePurpose) {
match purpose {
RemotePurpose::SetUpstreamPush => {
self.queue_network_op(
"push -u",
NetworkOp::PushSetUpstream {
remote: remote.to_owned(),
},
);
}
RemotePurpose::Pull => self.pull_from(remote),
}
}
pub(super) fn pull_rebase(&mut self) {
self.queue_network_op("pull --rebase", NetworkOp::PullRebase);
}
pub(super) fn pull_merge(&mut self) {
self.queue_network_op("pull", NetworkOp::PullMerge);
}
pub(crate) fn queue_network_op(&mut self, label: impl Into<String>, op: NetworkOp) {
match self.review.vcs.network_argv(op) {
Ok(argv) => self.queue_network(label, argv),
Err(err) => self.error(err.to_string()),
}
}
pub(crate) fn queue_network(&mut self, label: impl Into<String>, argv: Vec<String>) {
let label = label.into();
if argv.get(1).map(String::as_str) == Some("push") {
self.last_push_argv = Some(argv.clone());
}
self.info(format!("running git {label}…"));
self.pending_git = Some(GitOp { label, argv });
}
pub(super) fn declines_jj_network(&mut self) -> bool {
match self.review.vcs.network_argv(NetworkOp::Push) {
Ok(_) => false,
Err(err) => {
self.error(err.to_string());
true
}
}
}
pub(super) fn network_recovery(&mut self, label: &str, output: &str) -> bool {
let out = output.to_ascii_lowercase();
let no_upstream = out.contains("no upstream")
|| out.contains("no tracking information")
|| out.contains("set the upstream");
if label.starts_with("push") {
if label.contains("force") {
return false; }
if label == Self::PR_CREATE_PUSH {
return false;
}
if no_upstream {
self.push_set_upstream();
return true;
}
if out.contains("non-fast-forward")
|| out.contains("updates were rejected")
|| out.contains("fetch first")
|| out.contains("[rejected]")
{
let Some(argv) = self.last_push_argv.clone() else {
return false;
};
self.modal = Some(Modal::Confirm {
message: "The remote has commits you don't have. \
Force-push with --force-with-lease?"
.into(),
on_confirm: PendingOp::RunGit {
label: "push --force-with-lease".into(),
argv: with_force_lease(argv),
},
});
return true;
}
} else if label.starts_with("pull") {
if no_upstream {
self.pull();
return true;
}
if out.contains("diverg")
|| out.contains("reconcile")
|| out.contains("not possible to fast-forward")
{
let upstream = self
.head
.upstream
.clone()
.unwrap_or_else(|| "the remote".into());
self.modal = Some(Modal::PullDiverged { upstream });
return true;
}
}
false
}
}
fn with_force_lease(mut argv: Vec<String>) -> Vec<String> {
let at = argv
.iter()
.position(|a| a == "push")
.map_or(argv.len(), |i| i + 1);
argv.insert(at, "--force-with-lease".into());
argv
}