Skip to content

Commit

Permalink
git: implements in a better way push
Browse files Browse the repository at this point in the history
  • Loading branch information
Pilou97 committed Feb 23, 2024
1 parent c86d4dd commit 8c7480e
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 125 deletions.
18 changes: 1 addition & 17 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,6 @@ pub fn push_from_notes(git: &Git) {
.clone()
.unwrap_or(git.config.yggit.default_upstream.clone());

let local_remote_commit = git.find_local_remote_head(&origin, branch);
let remote_commit = git.find_remote_head(&origin, branch);
let local_commit = git.head_of(branch);

if local_remote_commit != remote_commit {
println!("cannot push {}", branch);
return;
}

if local_commit == remote_commit {
println!("{}:{} is up to date", origin, branch);
continue;
}

println!("pushing {}:{}", origin, branch);
git.push_force(&origin, branch);
println!("\r{}:{} pushed", origin, branch);
git.push_force_with_lease(&origin, branch);
}
}
185 changes: 77 additions & 108 deletions src/git/git.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::config::GitConfig;
use auth_git2::GitAuthenticator;
use git2::{Branch, BranchType, Oid, Repository, Signature};
use git2::{Branch, BranchType, Error, Oid, Repository, Signature};
use serde::{de::DeserializeOwned, Serialize};
use std::process::Command;

Expand All @@ -19,6 +19,13 @@ pub struct EnhancedCommit<N> {
pub note: Option<N>,
}

#[allow(dead_code)]
enum PushMode {
Normal,
Force,
ForceWithLease,
}

impl Git {
/// Open a repository at the given path
/// Also load the signature from the .gitconfig
Expand Down Expand Up @@ -87,121 +94,83 @@ impl Git {
commits
}

/// Returns the local id of the head of origin/{branch}
pub fn find_local_remote_head(&self, origin: &str, branch: &str) -> Option<Oid> {
let Self { repository, .. } = self;
// Get the reference of the branch
let reference = format!("refs/remotes/{}/{}", origin, branch);

// Get the head of this branch
repository
.find_reference(&reference)
.ok()
.and_then(|reference| reference.peel_to_commit().ok())
.map(|commit| commit.id())
}

/// Returns the remote head of origin/{branch}
///
/// It will fetch the repository
/// Get the head
/// Revert the fetch
pub fn find_remote_head(&self, origin: &str, branch: &str) -> Option<Oid> {
let Self { repository, .. } = self;
// Get the remote
let mut remote = repository.find_remote(origin).expect("remote not found");
// Get the reference of the branch
let reference = format!("refs/remotes/{}/{}", origin, branch);

// Get the head of this branch
let local_commit = repository
.find_reference(&reference)
.ok()
.and_then(|reference| reference.peel_to_commit().ok());

// Fetch the branch
self.auth
.fetch(
&self.repository,
&mut remote,
&[branch],
Some("fetch branch"),
)
.expect("fetch repository has fialed");

// Get the new head
let remote_commit = repository
.find_reference(&reference)
.ok()
.and_then(|reference| reference.peel_to_commit().ok());

// Get the reference object to the reference
let reference = repository.find_reference(&reference).ok();

// Change the reference to the old commit to revert the fetch

match (local_commit, remote_commit, reference) {
(None, None, None) => None,
(None, None, Some(_)) => {
println!("remote and reference should exists possible");
None
}
(None, Some(_), None) => {
println!("odd");
None
}
(None, Some(remote_commit), Some(_)) => {
println!("No local commits, but remote one");
Some(remote_commit.id())
}
(Some(_), None, None) => None,
(Some(local_commit), None, Some(mut reference)) => {
reference
.set_target(local_commit.id(), "revert fetch")
.expect("revert fetch error");
println!("reference and remote should exists");
None
}
(Some(_), Some(remote_commit), None) => {
println!("local commit exists, remote too, but no references...");
Some(remote_commit.id())
}
(Some(local_commit), Some(remote_commit), Some(mut reference)) => {
reference
.set_target(local_commit.id(), "revert fetch")
.expect("revert fetch error");
Some(remote_commit.id())
fn push(&self, origin: &str, branch: &str, mode: PushMode) {
println!("pushing {}:{}", origin, branch);
let fetch_refname = format!("refs/heads/{}", branch);
let git_config = self.repository.config().unwrap();
let mut push_options = git2::PushOptions::new();

let mut remote_callbacks = git2::RemoteCallbacks::new();
remote_callbacks.credentials(self.auth.credentials(&git_config));

remote_callbacks.push_negotiation(|remote_updates| {
let null = git2::Oid::zero();
for remote_update in remote_updates {
// It's a new branch
if remote_update.src() == null {
println!("{}:{} is a new branch", origin, branch);
return Ok(());
}
// No need to push
if remote_update.src() == remote_update.dst() {
println!("{}:{} is up to date", origin, branch);
return Err(git2::Error::from_str("no need to push"));
}
return match mode {
PushMode::Normal => {
// last commit of remote has to be known in current branch
Err(Error::from_str("not yet implemented"))
}
PushMode::Force => Ok(()),
PushMode::ForceWithLease => {
// Comparing src with local origin
let remote_origin_oid = remote_update.src();
// Get the head of this branch
let local_origin_oid = {
let local_origin_name = remote_update.src_refname().unwrap();
let upstream_name =
local_origin_name.strip_prefix("refs/heads/").unwrap();
self.repository
.find_reference(&format!(
"refs/remotes/{}/{}",
origin, upstream_name
))
.ok()
.and_then(|reference| reference.peel_to_commit().ok())
.map(|commit| commit.id())
.unwrap()
};
if remote_origin_oid == local_origin_oid {
Ok(())
} else {
println!("{}:{} have diverged, not pushing", origin, branch);
Err(Error::from_str("Origins have divered"))
}
}
};
}
}
}
println!("There were no negotiation");
Err(git2::Error::from_str("No negotiation"))
});

/// Returns the commit to head of branch and head of branch/origin
pub fn head_of(&self, branch: &str) -> Option<Oid> {
let local_reference_name = format!("refs/heads/{}", branch);
push_options.remote_callbacks(remote_callbacks);

// Get the local commit
self.repository
.find_reference(&local_reference_name)
.ok()
.and_then(|reference| reference.peel_to_commit().ok())
.map(|commit| commit.id())
}

/// Push force a branch
pub fn push_force(&self, origin: &str, branch: &str) {
let fetch_refname = format!("refs/heads/{}", branch);
let mut remote = self
.repository
.find_remote(origin)
.expect("Cannot find origin");
let result = remote.push(
&[format!("+{}", fetch_refname).as_str()],
Some(&mut push_options),
);
if result.is_ok() {
println!("{}:{} pushed", origin, branch);
}
return;
}

self.auth
.push(
&self.repository,
&mut remote,
&[format!("+{}", fetch_refname).as_str()],
)
.expect("push force failed");
pub fn push_force_with_lease(&self, origin: &str, branch: &str) {
self.push(origin, branch, PushMode::ForceWithLease)
}

/// Delete a note
Expand Down

0 comments on commit 8c7480e

Please sign in to comment.