use git2::{
Cred,
FetchOptions,
PushOptions,
RemoteCallbacks,
Signature,
Status,
};
use anyhow::bail;
use std::path::Path;
pub struct Repository {
path: String,
repo: Option< git2::Repository >,
}
impl Repository {
pub fn new( path: &str ) -> Self {
Self {
path: path.to_owned(),
repo: None,
}
}
pub fn open( &mut self ) -> anyhow::Result<()> {
let repo = match git2::Repository::discover( &self.path ) {
Ok(repo) => repo,
Err(e) => bail!("failed to open: {}", e),
};
dbg!(&repo.state());
self.repo = Some( repo );
Ok(())
}
pub fn get_dirty( &mut self ) -> Vec<String> {
match &self.repo {
Some( repo ) => {
let mut dirty = Vec::new();
let mut check_s = Status::empty();
check_s.insert(Status::INDEX_NEW);
check_s.insert(Status::INDEX_MODIFIED);
check_s.insert(Status::WT_MODIFIED);
let mut skip_s = Status::empty();
skip_s.insert(Status::IGNORED);
skip_s.insert(Status::WT_NEW);
for se in repo.statuses( None ).unwrap().iter() {
let s = se.status();
if s.intersects( check_s ) {
dirty.push( se.path().unwrap_or( "" ).to_owned());
} else {
if !s.intersects( skip_s ) {
println!("Not dirty {:?} {}", s, se.path().unwrap_or( "" ) );
}
}
}
dirty
},
None => {
Vec::new()
},
}
}
pub fn commit( &mut self, files: &Vec<String>, message: &str ) -> anyhow::Result<()> {
match &self.repo {
Some( repo ) => {
let sig = repo.signature()?;
let mut index = match repo.index() {
Err(e) => bail!("No index for repository {}", &e),
Ok(index) => index,
};
let cwd = match std::env::current_dir() {
Ok( wd ) => wd,
Err( e ) => bail!("No current working directory found"),
};
let rwd = match repo.workdir() {
Some( wd ) => wd,
None => bail!("No workdir for repository"),
};
for f in files.iter() {
let p = Path::new( &cwd ).join( &f );
let p = match p.strip_prefix( &rwd ) {
Ok( p ) => p,
Err( e ) => bail!("Error stripping {:?} from {:?}", &rwd, &p),
};
index.add_path( &p )?;
};
index.write()?;
let mut index = match repo.index() {
Err(e) => bail!("No index for repository {}", &e),
Ok(index) => index,
};
let oid = match index.write_tree() {
Ok( oid ) => oid,
Err( e ) => bail!("Error writing tree for repository: {}", &e ),
};
index.write()?;
let tree = match repo.find_tree(oid) {
Ok( tree ) => tree,
Err( e ) => bail!("Error findind tree for OID {}: {}", &oid, &e),
};
let parent = match repo.revparse_ext( "HEAD" ) {
Ok( ( object, _ ) ) => object,
Err( e ) => bail!( "Error finding HEAD {}", &e ),
};
let parent = match parent.as_commit() {
Some( commit ) => commit,
None => bail!( "Parent is not a commit" ),
};
repo.commit(
Some("HEAD"),
&sig,
&sig,
message,
&tree,
&[ parent ],
)?;
Ok(())
},
None => bail!( "No repo open for commit" ),
}
}
pub fn tag( &mut self, tag: &str, msg: &str ) -> anyhow::Result<()> {
match &self.repo {
Some( repo ) => {
let rv = repo.revparse( "HEAD" )?;
let ho = match rv.from() {
Some( ho ) => ho,
None => bail!( "No HEAD found!" ),
};
println!("Tagging {} with {}", &ho.id(), &tag);
dbg!(&ho);
let sig = repo.signature()?;
let tag_oid = repo.tag( tag, &ho, &sig, msg, true )?;
dbg!(&tag_oid);
Ok(())
},
None => bail!( "No repo open for tag" ),
}
}
pub fn fetch( &mut self ) -> anyhow::Result<(usize)> {
match &self.repo {
Some( repo ) => {
let remote_name = "origin";
let mut remote = match repo.find_remote( &remote_name ) {
Ok( remote ) => remote,
Err( e ) => bail!( "Couldn't find remote({}): {}", &remote_name, &e ),
};
let mut cbs = RemoteCallbacks::new();
cbs.credentials(|_url, username_from_url, _allowed_types| {
dbg!(&username_from_url);
Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!("{}/.ssh/id_ed25519", std::env::var("HOME").unwrap())),
None,
)
});
cbs.transfer_progress(|progress| {
println!("Transfer progress: {}", progress.received_bytes());
println!("{}/{} objects", progress.received_objects(), progress.total_objects());
true
});
let mut opts = FetchOptions::new();
opts.remote_callbacks( cbs );
remote.fetch(&["main"], Some( &mut opts ), None)?;
let stats = remote.stats();
println!("Fetched {} bytes.", stats.received_bytes());
println!("Fetched {} objects.", stats.received_objects());
Ok(stats.total_objects())
},
None => bail!( "No repo open for fetch" ),
}
}
pub fn rebase( &mut self ) -> anyhow::Result<()> {
match &self.repo {
Some( repo ) => {
let head = repo.head()?; let upstream = repo.reference_to_annotated_commit( &head )?;
let rv = repo.revparse( "origin/HEAD" )?;
let oho = match rv.from() {
Some( oho ) => oho,
None => bail!( "No origin/HEAD found!" ),
};
let upstream = match repo.find_annotated_commit( oho.id() ) {
Ok( commit ) => commit,
Err( e ) => bail!( "No commit for origin/HEAD! {}", &e ),
};
println!("Rebasing on upstream {} {}", upstream.id(), "" ); let mut rebase = repo.rebase( None, Some( &upstream ), None, None )?;
println!("{}", rebase.len());
while let Some( ro ) = rebase.next() {
match ro {
Ok( ro ) => {
dbg!( &ro );
let sig = repo.signature()?;
match rebase.commit(
None,
&sig,
None,
) {
Ok( r ) => {
dbg!( &r );
},
Err( e ) => {
dbg!( &e );
},
}
},
Err( e ) => {
dbg!( &e );
},
}
}
rebase.finish(None)?;
},
None => bail!( "No repo open for rebase" ),
}
Ok(())
}
pub fn push( &mut self ) -> anyhow::Result<(usize)> {
match &self.repo {
Some( repo ) => {
let remote_name = "origin";
let mut remote = match repo.find_remote( &remote_name ) {
Ok( remote ) => remote,
Err( e ) => bail!( "Couldn't find remote({}): {}", &remote_name, &e ),
};
let mut cbs = RemoteCallbacks::new();
cbs.credentials(|_url, username_from_url, _allowed_types| {
dbg!(&username_from_url);
Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!("{}/.ssh/id_ed25519", std::env::var("HOME").unwrap())),
None,
)
});
cbs.transfer_progress(|progress| {
println!("Transfer progress: {}", progress.received_bytes());
println!("{}/{} objects", progress.received_objects(), progress.total_objects());
true
});
let mut opts = PushOptions::new();
opts.remote_callbacks( cbs );
remote.push(
&["refs/heads/main"],
Some( &mut opts )
)?;
Ok( 0 )
},
None => bail!( "No repo open for push" ),
}
}
pub fn push_tag( &mut self, tag: &str ) -> anyhow::Result<(usize)> {
match &self.repo {
Some( repo ) => {
let remote_name = "origin";
let mut remote = match repo.find_remote( &remote_name ) {
Ok( remote ) => remote,
Err( e ) => bail!( "Couldn't find remote({}): {}", &remote_name, &e ),
};
let mut cbs = RemoteCallbacks::new();
cbs.credentials(|_url, username_from_url, _allowed_types| {
Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!("{}/.ssh/id_ed25519", std::env::var("HOME").unwrap())),
None,
)
});
cbs.transfer_progress(|progress| {
println!("Transfer progress: {}", progress.received_bytes());
println!("{}/{} objects", progress.received_objects(), progress.total_objects());
true
});
cbs.push_update_reference(|name, status|{
println!("Push Update Reference: {} -> {:?}", name, status);
Ok(())
});
let mut opts = PushOptions::new();
opts.remote_callbacks( cbs );
let tag_ref = format!("refs/tags/{}", &tag);
println!("Pushing ref {}", &tag_ref);
remote.push(
&[ &tag_ref ],
Some( &mut opts )
)?;
Ok( 0 )
},
None => bail!( "No repo open for push" ),
}
}
}