summaryrefslogtreecommitdiffstats
path: root/asyncgit/src/sync/remotes/push.rs
diff options
context:
space:
mode:
Diffstat (limited to 'asyncgit/src/sync/remotes/push.rs')
-rw-r--r--asyncgit/src/sync/remotes/push.rs904
1 files changed, 452 insertions, 452 deletions
diff --git a/asyncgit/src/sync/remotes/push.rs b/asyncgit/src/sync/remotes/push.rs
index df0d7cfe..6cef918e 100644
--- a/asyncgit/src/sync/remotes/push.rs
+++ b/asyncgit/src/sync/remotes/push.rs
@@ -1,11 +1,11 @@
use super::utils;
use crate::{
- error::{Error, Result},
- progress::ProgressPercent,
- sync::{
- branch::branch_set_upstream, cred::BasicAuthCredential,
- remotes::Callbacks, CommitId,
- },
+ error::{Error, Result},
+ progress::ProgressPercent,
+ sync::{
+ branch::branch_set_upstream, cred::BasicAuthCredential,
+ remotes::Callbacks, CommitId,
+ },
};
use crossbeam_channel::Sender;
use git2::{PackBuilderStage, PushOptions};
@@ -13,467 +13,467 @@ use scopetime::scope_time;
///
pub trait AsyncProgress: Clone + Send + Sync {
- ///
- fn is_done(&self) -> bool;
- ///
- fn progress(&self) -> ProgressPercent;
+ ///
+ fn is_done(&self) -> bool;
+ ///
+ fn progress(&self) -> ProgressPercent;
}
///
#[derive(Debug, Clone, PartialEq)]
pub enum ProgressNotification {
- ///
- UpdateTips {
- ///
- name: String,
- ///
- a: CommitId,
- ///
- b: CommitId,
- },
- ///
- Transfer {
- ///
- objects: usize,
- ///
- total_objects: usize,
- },
- ///
- PushTransfer {
- ///
- current: usize,
- ///
- total: usize,
- ///
- bytes: usize,
- },
- ///
- Packing {
- ///
- stage: PackBuilderStage,
- ///
- total: usize,
- ///
- current: usize,
- },
- ///
- Done,
+ ///
+ UpdateTips {
+ ///
+ name: String,
+ ///
+ a: CommitId,
+ ///
+ b: CommitId,
+ },
+ ///
+ Transfer {
+ ///
+ objects: usize,
+ ///
+ total_objects: usize,
+ },
+ ///
+ PushTransfer {
+ ///
+ current: usize,
+ ///
+ total: usize,
+ ///
+ bytes: usize,
+ },
+ ///
+ Packing {
+ ///
+ stage: PackBuilderStage,
+ ///
+ total: usize,
+ ///
+ current: usize,
+ },
+ ///
+ Done,
}
impl AsyncProgress for ProgressNotification {
- fn is_done(&self) -> bool {
- *self == Self::Done
- }
- fn progress(&self) -> ProgressPercent {
- match *self {
- Self::Packing {
- stage,
- current,
- total,
- } => match stage {
- PackBuilderStage::AddingObjects
- | PackBuilderStage::Deltafication => {
- ProgressPercent::new(current, total)
- }
- },
- Self::PushTransfer { current, total, .. } => {
- ProgressPercent::new(current, total)
- }
- Self::Transfer {
- objects,
- total_objects,
- ..
- } => ProgressPercent::new(objects, total_objects),
- _ => ProgressPercent::full(),
- }
- }
+ fn is_done(&self) -> bool {
+ *self == Self::Done
+ }
+ fn progress(&self) -> ProgressPercent {
+ match *self {
+ Self::Packing {
+ stage,
+ current,
+ total,
+ } => match stage {
+ PackBuilderStage::AddingObjects
+ | PackBuilderStage::Deltafication => {
+ ProgressPercent::new(current, total)
+ }
+ },
+ Self::PushTransfer { current, total, .. } => {
+ ProgressPercent::new(current, total)
+ }
+ Self::Transfer {
+ objects,
+ total_objects,
+ ..
+ } => ProgressPercent::new(objects, total_objects),
+ _ => ProgressPercent::full(),
+ }
+ }
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn push(
- repo_path: &str,
- remote: &str,
- branch: &str,
- force: bool,
- delete: bool,
- basic_credential: Option<BasicAuthCredential>,
- progress_sender: Option<Sender<ProgressNotification>>,
+ repo_path: &str,
+ remote: &str,
+ branch: &str,
+ force: bool,
+ delete: bool,
+ basic_credential: Option<BasicAuthCredential>,
+ progress_sender: Option<Sender<ProgressNotification>>,
) -> Result<()> {
- scope_time!("push");
-
- let repo = utils::repo(repo_path)?;
- let mut remote = repo.find_remote(remote)?;
-
- let mut options = PushOptions::new();
-
- let callbacks = Callbacks::new(progress_sender, basic_credential);
- options.remote_callbacks(callbacks.callbacks());
- options.packbuilder_parallelism(0);
-
- let branch_modifier = match (force, delete) {
- (true, true) => "+:",
- (false, true) => ":",
- (true, false) => "+",
- (false, false) => "",
- };
- let branch_name =
- format!("{}refs/heads/{}", branch_modifier, branch);
- remote.push(&[branch_name.as_str()], Some(&mut options))?;
-
- if let Some((reference, msg)) =
- callbacks.get_stats()?.push_rejected_msg
- {
- return Err(Error::Generic(format!(
- "push to '{}' rejected: {}",
- reference, msg
- )));
- }
-
- if !delete {
- branch_set_upstream(&repo, branch)?;
- }
-
- Ok(())
+ scope_time!("push");
+
+ let repo = utils::repo(repo_path)?;
+ let mut remote = repo.find_remote(remote)?;
+
+ let mut options = PushOptions::new();
+
+ let callbacks = Callbacks::new(progress_sender, basic_credential);
+ options.remote_callbacks(callbacks.callbacks());
+ options.packbuilder_parallelism(0);
+
+ let branch_modifier = match (force, delete) {
+ (true, true) => "+:",
+ (false, true) => ":",
+ (true, false) => "+",
+ (false, false) => "",
+ };
+ let branch_name =
+ format!("{}refs/heads/{}", branch_modifier, branch);
+ remote.push(&[branch_name.as_str()], Some(&mut options))?;
+
+ if let Some((reference, msg)) =
+ callbacks.get_stats()?.push_rejected_msg
+ {
+ return Err(Error::Generic(format!(
+ "push to '{}' rejected: {}",
+ reference, msg
+ )));
+ }
+
+ if !delete {
+ branch_set_upstream(&repo, branch)?;
+ }
+
+ Ok(())
}
#[cfg(test)]
mod tests {
- use super::*;
- use crate::sync::{
- self,
- tests::{
- get_commit_ids, repo_clone, repo_init, repo_init_bare,
- write_commit_file,
- },
- };
- use git2::Repository;
- use std::{fs::File, io::Write, path::Path};
-
- #[test]
- fn test_force_push() {
- // This test mimics the scenario of 2 people having 2
- // local branches and both modifying the same file then
- // both pushing, sequentially
- let (tmp_repo_dir, repo) = repo_init().unwrap();
- let (tmp_other_repo_dir, other_repo) = repo_init().unwrap();
- let (tmp_upstream_dir, _) = repo_init_bare().unwrap();
-
- repo.remote(
- "origin",
- tmp_upstream_dir.path().to_str().unwrap(),
- )
- .unwrap();
-
- other_repo
- .remote(
- "origin",
- tmp_upstream_dir.path().to_str().unwrap(),
- )
- .unwrap();
-
- let tmp_repo_file_path =
- tmp_repo_dir.path().join("temp_file.txt");
- let mut tmp_repo_file =
- File::create(tmp_repo_file_path).unwrap();
- writeln!(tmp_repo_file, "TempSomething").unwrap();
-
- sync::commit(
- tmp_repo_dir.path().to_str().unwrap(),
- "repo_1_commit",
- )
- .unwrap();
-
- push(
- tmp_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- false,
- false,
- None,
- None,
- )
- .unwrap();
-
- let tmp_other_repo_file_path =
- tmp_other_repo_dir.path().join("temp_file.txt");
- let mut tmp_other_repo_file =
- File::create(tmp_other_repo_file_path).unwrap();
- writeln!(tmp_other_repo_file, "TempElse").unwrap();
-
- sync::commit(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "repo_2_commit",
- )
- .unwrap();
-
- // Attempt a normal push,
- // should fail as branches diverged
- assert_eq!(
- push(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- false,
- false,
- None,
- None,
- )
- .is_err(),
- true
- );
-
- // Attempt force push,
- // should work as it forces the push through
- assert_eq!(
- push(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- true,
- false,
- None,
- None,
- )
- .is_err(),
- false
- );
- }
-
- #[test]
- fn test_force_push_rewrites_history() {
- // This test mimics the scenario of 2 people having 2
- // local branches and both modifying the same file then
- // both pushing, sequentially
-
- let (tmp_repo_dir, repo) = repo_init().unwrap();
- let (tmp_other_repo_dir, other_repo) = repo_init().unwrap();
- let (tmp_upstream_dir, upstream) = repo_init_bare().unwrap();
-
- repo.remote(
- "origin",
- tmp_upstream_dir.path().to_str().unwrap(),
- )
- .unwrap();
-
- other_repo
- .remote(
- "origin",
- tmp_upstream_dir.path().to_str().unwrap(),
- )
- .unwrap();
-
- let tmp_repo_file_path =
- tmp_repo_dir.path().join("temp_file.txt");
- let mut tmp_repo_file =
- File::create(tmp_repo_file_path).unwrap();
- writeln!(tmp_repo_file, "TempSomething").unwrap();
-
- sync::stage_add_file(
- tmp_repo_dir.path().to_str().unwrap(),
- Path::new("temp_file.txt"),
- )
- .unwrap();
-
- let repo_1_commit = sync::commit(
- tmp_repo_dir.path().to_str().unwrap(),
- "repo_1_commit",
- )
- .unwrap();
-
- //NOTE: make sure the commit actually contains that file
- assert_eq!(
- sync::get_commit_files(
- tmp_repo_dir.path().to_str().unwrap(),
- repo_1_commit
- )
- .unwrap()[0]
- .path,
- String::from("temp_file.txt")
- );
-
- let commits = get_commit_ids(&repo, 1);
- assert!(commits.contains(&repo_1_commit));
-
- push(
- tmp_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- false,
- false,
- None,
- None,
- )
- .unwrap();
-
- let tmp_other_repo_file_path =
- tmp_other_repo_dir.path().join("temp_file.txt");
- let mut tmp_other_repo_file =
- File::create(tmp_other_repo_file_path).unwrap();
- writeln!(tmp_other_repo_file, "TempElse").unwrap();
-
- sync::stage_add_file(
- tmp_other_repo_dir.path().to_str().unwrap(),
- Path::new("temp_file.txt"),
- )
- .unwrap();
-
- let repo_2_commit = sync::commit(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "repo_2_commit",
- )
- .unwrap();
-
- let repo_2_parent = other_repo
- .find_commit(repo_2_commit.into())
- .unwrap()
- .parents()
- .next()
- .unwrap()
- .id();
-
- let commits = get_commit_ids(&other_repo, 1);
- assert!(commits.contains(&repo_2_commit));
-
- // Attempt a normal push,
- // should fail as branches diverged
- assert_eq!(
- push(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- false,
- false,
- None,
- None,
- )
- .is_err(),
- true
- );
-
- // Check that the other commit is not in upstream,
- // a normal push would not rewrite history
- let commits = get_commit_ids(&upstream, 1);
- assert!(!commits.contains(&repo_2_commit));
-
- // Attempt force push,
- // should work as it forces the push through
-
- push(
- tmp_other_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- true,
- false,
- None,
- None,
- )
- .unwrap();
-
- let commits = get_commit_ids(&upstream, 1);
- assert!(commits.contains(&repo_2_commit));
-
- let new_upstream_parent =
- Repository::init_bare(tmp_upstream_dir.path())
- .unwrap()
- .find_commit(repo_2_commit.into())
- .unwrap()
- .parents()
- .next()
- .unwrap()
- .id();
- assert_eq!(new_upstream_parent, repo_2_parent,);
- }
-
- #[test]
- fn test_delete_remote_branch() {
- // This test mimics the scenario of a user creating a branch, push it, and then remove it on the remote
-
- let (upstream_dir, upstream_repo) = repo_init_bare().unwrap();
-
- let (tmp_repo_dir, repo) =
- repo_clone(upstream_dir.path().to_str().unwrap())
- .unwrap();
-
- // You need a commit before being able to branch !
- let commit_1 = write_commit_file(
- &repo,
- "temp_file.txt",
- "SomeContent",
- "Initial commit",
- );
-
- let commits = get_commit_ids(&repo, 1);
- assert!(commits.contains(&commit_1));
-
- push(
- tmp_repo_dir.path().to_str().unwrap(),
- "origin",
- "master",
- false,
- false,
- None,
- None,
- )
- .unwrap();
-
- // Create the local branch
- sync::create_branch(
- tmp_repo_dir.path().to_str().unwrap(),
- "test_branch",
- )
- .unwrap();
-
- // Push the local branch
- push(
- tmp_repo_dir.path().to_str().unwrap(),
- "origin",
- "test_branch",
- false,
- false,
- None,
- None,
- )
- .unwrap();
-
- // Test if the branch exits on the remote
- assert_eq!(
- upstream_repo
- .branches(None)
- .unwrap()
- .map(|i| i.unwrap())
- .map(|(i, _)| i.name().unwrap().unwrap().to_string())
- .filter(|i| i == "test_branch")
- .next()
- .is_some(),
- true
- );
-
- // Delete the remote branch
- assert_eq!(
- push(
- tmp_repo_dir.path().to_str().unwrap(),
- "origin",
- "test_branch",
- false,
- true,
- None,
- None,
- )
- .is_ok(),
- true
- );
-
- // Test that the branch has be remove from the remote
- assert_eq!(
- upstream_repo
- .branches(None)
- .unwrap()
- .map(|i| i.unwrap())
- .map(|(i, _)| i.name().unwrap().unwrap().to_string())
- .filter(|i| i == "test_branch")
- .next()
- .is_some(),
- false
- );
- }
+ use super::*;
+ use crate::sync::{
+ self,
+ tests::{
+ get_commit_ids, repo_clone, repo_init, repo_init_bare,
+ write_commit_file,
+ },
+ };
+ use git2::Repository;
+ use std::{fs::File, io::Write, path::Path};
+
+ #[test]
+ fn test_force_push() {
+ // This test mimics the scenario of 2 people having 2
+ // local branches and both modifying the same file then
+ // both pushing, sequentially
+ let (tmp_repo_dir, repo) = repo_init().unwrap();
+ let (tmp_other_repo_dir, other_repo) = repo_init().unwrap();
+ let (tmp_upstream_dir, _) = repo_init_bare().unwrap();
+
+ repo.remote(
+ "origin",
+ tmp_upstream_dir.path().to_str().unwrap(),
+ )
+ .unwrap();
+
+ other_repo
+ .remote(
+ "origin",
+ tmp_upstream_dir.path().to_str().unwrap(),
+ )
+ .unwrap();
+
+ let tmp_repo_file_path =
+ tmp_repo_dir.path().join("temp_file.txt");
+ let mut tmp_repo_file =
+ File::create(tmp_repo_file_path).unwrap();
+ writeln!(tmp_repo_file, "TempSomething").unwrap();
+
+ sync::commit(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "repo_1_commit",
+ )
+ .unwrap();
+
+ push(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ false,
+ false,
+ None,
+ None,
+ )
+ .unwrap();
+
+ let tmp_other_repo_file_path =
+ tmp_other_repo_dir.path().join("temp_file.txt");
+ let mut tmp_other_repo_file =
+ File::create(tmp_other_repo_file_path).unwrap();
+ writeln!(tmp_other_repo_file, "TempElse").unwrap();
+
+ sync::commit(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "repo_2_commit",
+ )
+ .unwrap();
+
+ // Attempt a normal push,
+ // should fail as branches diverged
+ assert_eq!(
+ push(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ false,
+ false,
+ None,
+ None,
+ )
+ .is_err(),
+ true
+ );
+
+ // Attempt force push,
+ // should work as it forces the push through
+ assert_eq!(
+ push(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ true,
+ false,
+ None,
+ None,
+ )
+ .is_err(),
+ false
+ );
+ }
+
+ #[test]
+ fn test_force_push_rewrites_history() {
+ // This test mimics the scenario of 2 people having 2
+ // local branches and both modifying the same file then
+ // both pushing, sequentially
+
+ let (tmp_repo_dir, repo) = repo_init().unwrap();
+ let (tmp_other_repo_dir, other_repo) = repo_init().unwrap();
+ let (tmp_upstream_dir, upstream) = repo_init_bare().unwrap();
+
+ repo.remote(
+ "origin",
+ tmp_upstream_dir.path().to_str().unwrap(),
+ )
+ .unwrap();
+
+ other_repo
+ .remote(
+ "origin",
+ tmp_upstream_dir.path().to_str().unwrap(),
+ )
+ .unwrap();
+
+ let tmp_repo_file_path =
+ tmp_repo_dir.path().join("temp_file.txt");
+ let mut tmp_repo_file =
+ File::create(tmp_repo_file_path).unwrap();
+ writeln!(tmp_repo_file, "TempSomething").unwrap();
+
+ sync::stage_add_file(
+ tmp_repo_dir.path().to_str().unwrap(),
+ Path::new("temp_file.txt"),
+ )
+ .unwrap();
+
+ let repo_1_commit = sync::commit(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "repo_1_commit",
+ )
+ .unwrap();
+
+ //NOTE: make sure the commit actually contains that file
+ assert_eq!(
+ sync::get_commit_files(
+ tmp_repo_dir.path().to_str().unwrap(),
+ repo_1_commit
+ )
+ .unwrap()[0]
+ .path,
+ String::from("temp_file.txt")
+ );
+
+ let commits = get_commit_ids(&repo, 1);
+ assert!(commits.contains(&repo_1_commit));
+
+ push(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ false,
+ false,
+ None,
+ None,
+ )
+ .unwrap();
+
+ let tmp_other_repo_file_path =
+ tmp_other_repo_dir.path().join("temp_file.txt");
+ let mut tmp_other_repo_file =
+ File::create(tmp_other_repo_file_path).unwrap();
+ writeln!(tmp_other_repo_file, "TempElse").unwrap();
+
+ sync::stage_add_file(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ Path::new("temp_file.txt"),
+ )
+ .unwrap();
+
+ let repo_2_commit = sync::commit(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "repo_2_commit",
+ )
+ .unwrap();
+
+ let repo_2_parent = other_repo
+ .find_commit(repo_2_commit.into())
+ .unwrap()
+ .parents()
+ .next()
+ .unwrap()
+ .id();
+
+ let commits = get_commit_ids(&other_repo, 1);
+ assert!(commits.contains(&repo_2_commit));
+
+ // Attempt a normal push,
+ // should fail as branches diverged
+ assert_eq!(
+ push(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ false,
+ false,
+ None,
+ None,
+ )
+ .is_err(),
+ true
+ );
+
+ // Check that the other commit is not in upstream,
+ // a normal push would not rewrite history
+ let commits = get_commit_ids(&upstream, 1);
+ assert!(!commits.contains(&repo_2_commit));
+
+ // Attempt force push,
+ // should work as it forces the push through
+
+ push(
+ tmp_other_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ true,
+ false,
+ None,
+ None,
+ )
+ .unwrap();
+
+ let commits = get_commit_ids(&upstream, 1);
+ assert!(commits.contains(&repo_2_commit));
+
+ let new_upstream_parent =
+ Repository::init_bare(tmp_upstream_dir.path())
+ .unwrap()
+ .find_commit(repo_2_commit.into())
+ .unwrap()
+ .parents()
+ .next()
+ .unwrap()
+ .id();
+ assert_eq!(new_upstream_parent, repo_2_parent,);
+ }
+
+ #[test]
+ fn test_delete_remote_branch() {
+ // This test mimics the scenario of a user creating a branch, push it, and then remove it on the remote
+
+ let (upstream_dir, upstream_repo) = repo_init_bare().unwrap();
+
+ let (tmp_repo_dir, repo) =
+ repo_clone(upstream_dir.path().to_str().unwrap())
+ .unwrap();
+
+ // You need a commit before being able to branch !
+ let commit_1 = write_commit_file(
+ &repo,
+ "temp_file.txt",
+ "SomeContent",
+ "Initial commit",
+ );
+
+ let commits = get_commit_ids(&repo, 1);
+ assert!(commits.contains(&commit_1));
+
+ push(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "master",
+ false,
+ false,
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Create the local branch
+ sync::create_branch(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "test_branch",
+ )
+ .unwrap();
+
+ // Push the local branch
+ push(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "test_branch",
+ false,
+ false,
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Test if the branch exits on the remote
+ assert_eq!(
+ upstream_repo
+ .branches(None)
+ .unwrap()
+ .map(|i| i.unwrap())
+ .map(|(i, _)| i.name().unwrap().unwrap().to_string())
+ .filter(|i| i == "test_branch")
+ .next()
+ .is_some(),
+ true
+ );
+
+ // Delete the remote branch
+ assert_eq!(
+ push(
+ tmp_repo_dir.path().to_str().unwrap(),
+ "origin",
+ "test_branch",
+ false,
+ true,
+ None,
+ None,
+ )
+ .is_ok(),
+ true
+ );
+
+ // Test that the branch has be remove from the remote
+ assert_eq!(
+ upstream_repo
+ .branches(None)
+ .unwrap()
+ .map(|i| i.unwrap())
+ .map(|(i, _)| i.name().unwrap().unwrap().to_string())
+ .filter(|i| i == "test_branch")
+ .next()
+ .is_some(),
+ false
+ );
+ }
}