Allow pushing after staging.
parent
014a7d72e5
commit
c0d0c0d7ba
|
@ -20,7 +20,7 @@ packages:
|
|||
- $HOME/.bash_profile
|
||||
- $HOME/.bashrc
|
||||
home-manager:
|
||||
- $HOME/home.nix
|
||||
- $HOME/.config/nixpkgs/home.nix
|
||||
homesync:
|
||||
- $HOME/.homesync.yml
|
||||
- $HOME/.config/homesync/homesync.yml
|
||||
|
|
477
src/git.rs
477
src/git.rs
|
@ -1,10 +1,11 @@
|
|||
use super::{config::PathConfig, path};
|
||||
use git2::{
|
||||
Branch, BranchType, Commit, Cred, DiffOptions, Direction, FetchOptions, Index, IndexAddOption,
|
||||
ObjectType, Remote, RemoteCallbacks, Repository, Signature,
|
||||
BranchType, Commit, Cred, DiffOptions, Direction, FetchOptions, Index, IndexAddOption,
|
||||
ObjectType, PushOptions, Remote, RemoteCallbacks, Repository, Signature, StashApplyOptions,
|
||||
StashFlags,
|
||||
};
|
||||
use path::ResPathBuf;
|
||||
use simplelog::{info, paris};
|
||||
use simplelog::{info, paris, warn};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env::VarError,
|
||||
|
@ -76,20 +77,20 @@ impl error::Error for Error {}
|
|||
// ========================================
|
||||
|
||||
fn clone(pc: &PathConfig, expanded: &Path) -> Result<Repository> {
|
||||
let fetch_options = get_fetch_options(&pc)?;
|
||||
let fetch_options = get_fetch_options(pc)?;
|
||||
let mut builder = git2::build::RepoBuilder::new();
|
||||
builder.fetch_options(fetch_options);
|
||||
|
||||
Ok(builder.clone(&pc.config.repos.remote.url, &expanded)?)
|
||||
}
|
||||
|
||||
// TODO(jrpotter): Setup a sentinel file in the given repository. This is used
|
||||
// for both ensuring any remote repositories are already managed by homesync and
|
||||
// for storing any persisted configurations.
|
||||
|
||||
/// Sets up a local github repository all configuration files will be synced to.
|
||||
/// If there does not exist a local repository at the requested location, we
|
||||
/// attempt to make it via cloning or initializing.
|
||||
///
|
||||
/// TODO(jrpotter): Setup a sentinel file in the given repository. This is used
|
||||
/// for both ensuring any remote repositories are already managed by homesync
|
||||
/// and for storing any persisted configurations.
|
||||
pub fn init(pc: &PathConfig) -> Result<Repository> {
|
||||
// Permit the use of environment variables within the local configuration
|
||||
// path (e.g. `$HOME`). Unlike with resolution, we want to fail if the
|
||||
|
@ -111,7 +112,7 @@ pub fn init(pc: &PathConfig) -> Result<Repository> {
|
|||
);
|
||||
Ok(repo)
|
||||
}
|
||||
Err(e) if e.code() == git2::ErrorCode::NotFound => match clone(&pc, &expanded) {
|
||||
Err(e) if e.code() == git2::ErrorCode::NotFound => match clone(pc, &expanded) {
|
||||
Ok(repo) => {
|
||||
info!(
|
||||
"Cloned remote repository <green>{}</>.",
|
||||
|
@ -120,7 +121,7 @@ pub fn init(pc: &PathConfig) -> Result<Repository> {
|
|||
Ok(repo)
|
||||
}
|
||||
Err(Error::GitError(e))
|
||||
if e.code() == git2::ErrorCode::Eof && e.class() == git2::ErrorClass::Ssh =>
|
||||
if e.class() == git2::ErrorClass::Ssh && e.code() == git2::ErrorCode::Eof =>
|
||||
{
|
||||
info!(
|
||||
"Creating local repository at <green>{}</>.",
|
||||
|
@ -170,9 +171,9 @@ fn find_package_files(pc: &PathConfig) -> Vec<ResPathBuf> {
|
|||
}
|
||||
|
||||
pub fn stage(pc: &PathConfig, repo: &Repository) -> Result<()> {
|
||||
let workdir = validate_repo(&repo)?;
|
||||
let workdir = check_working_repo(repo)?;
|
||||
let repo_files = find_repo_files(&workdir)?;
|
||||
let package_files = find_package_files(&pc);
|
||||
let package_files = find_package_files(pc);
|
||||
|
||||
// Find all files in our repository that are no longer being referenced in
|
||||
// our primary config file. They should be removed from the repository.
|
||||
|
@ -207,6 +208,11 @@ pub fn stage(pc: &PathConfig, repo: &Repository) -> Result<()> {
|
|||
fs::copy(package_file.resolved(), copy)?;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Staged files. Run <italic>git -C <green>{}</> <italic>status</> to see what changed.",
|
||||
&pc.config.repos.local.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -214,44 +220,268 @@ pub fn stage(pc: &PathConfig, repo: &Repository) -> Result<()> {
|
|||
// Syncing
|
||||
// ========================================
|
||||
|
||||
/// Adds all files to our index.
|
||||
///
|
||||
/// Checks explicitly if any changes have been detected in the newly constructed
|
||||
/// index. If not, return `None`.
|
||||
pub fn index_add(repo: &Repository) -> Result<Option<Index>> {
|
||||
pub fn push(pc: &PathConfig, repo: &mut Repository) -> Result<()> {
|
||||
// First pull to make sure there are no conflicts when we push our changes.
|
||||
// This will also perform validation and construct our local and remote
|
||||
// environment.
|
||||
pull(pc, repo)?;
|
||||
|
||||
let refspec = format!("refs/heads/{}", &pc.config.repos.remote.branch);
|
||||
repo.set_head(&refspec)?;
|
||||
|
||||
// The index corresponds to our staging area. We add all files and write out
|
||||
// to a tree. The resulting tree can be found using `git ls-tree <oid>`.
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
|
||||
let mut index = match index_with_all(repo)? {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
warn!("Nothing to push. Have you run `homesync stage`?");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let index_oid = index.write_tree()?;
|
||||
// Want to also reflect this change on the working directory.
|
||||
index.write()?;
|
||||
let index_tree = repo.find_tree(index_oid)?;
|
||||
info!("Writing index to tree `{}`.", index_oid);
|
||||
|
||||
// Commit our changes and push them to our remote.
|
||||
// TODO(jrpotter): Come up with a more useful message.
|
||||
let signature = now_signature(pc)?;
|
||||
let message = "Automated homesync commit.";
|
||||
let commit_oid = if let Some(commit) = get_commit_at_head(repo) {
|
||||
repo.commit(
|
||||
Some(&refspec),
|
||||
&signature,
|
||||
&signature,
|
||||
message,
|
||||
&index_tree,
|
||||
&[&commit],
|
||||
)?
|
||||
} else {
|
||||
repo.commit(
|
||||
Some(&refspec),
|
||||
&signature,
|
||||
&signature,
|
||||
message,
|
||||
&index_tree,
|
||||
&[],
|
||||
)?
|
||||
};
|
||||
info!("Commited `{}` with message \"{}\".", commit_oid, message);
|
||||
|
||||
let mut remote = find_remote(pc, repo)?;
|
||||
let call_options = get_remote_callbacks(pc)?;
|
||||
remote.connect_auth(Direction::Push, Some(call_options), None)?;
|
||||
|
||||
let mut push_options = get_push_options(pc)?;
|
||||
remote.push(&[&format!("{r}:{r}", r = refspec)], Some(&mut push_options))?;
|
||||
info!(
|
||||
"Pushed changes to remote `{}`.",
|
||||
pc.config.repos.remote.tracking_branch(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn local_from_remote(pc: &PathConfig, repo: &Repository) -> Result<()> {
|
||||
fetch_remote(pc, repo)?;
|
||||
|
||||
let tracking_branch = pc.config.repos.remote.tracking_branch();
|
||||
let remote_branch = repo.find_branch(&tracking_branch, BranchType::Remote)?;
|
||||
let remote_ref = repo.reference_to_annotated_commit(remote_branch.get())?;
|
||||
|
||||
// It should never be the case this function is called when the local branch
|
||||
// exists. Keep `force` to `false` to catch any misuse here.
|
||||
repo.branch_from_annotated_commit(&pc.config.repos.remote.branch, &remote_ref, false)?;
|
||||
info!("Created new local branch from `{}`.", &tracking_branch);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn local_rebase_remote(pc: &PathConfig, repo: &Repository) -> Result<()> {
|
||||
fetch_remote(pc, repo)?;
|
||||
|
||||
let tracking_branch = pc.config.repos.remote.tracking_branch();
|
||||
let remote_branch = repo.find_branch(&tracking_branch, BranchType::Remote)?;
|
||||
let remote_ref = repo.reference_to_annotated_commit(remote_branch.get())?;
|
||||
|
||||
// Our remote branch after fetching should exist at the fetch. We could just
|
||||
// rebase onto the remote branch directly, but let's keep things local when
|
||||
// we can.
|
||||
let local_branch = repo.find_branch(&pc.config.repos.remote.branch, BranchType::Local)?;
|
||||
let local_ref = repo.reference_to_annotated_commit(local_branch.get())?;
|
||||
|
||||
let signature = now_signature(pc)?;
|
||||
repo.rebase(Some(&local_ref), Some(&remote_ref), None, None)?
|
||||
.finish(Some(&signature))?;
|
||||
info!("Rebased local branch onto `{}`.", &tracking_branch);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pull(pc: &PathConfig, repo: &mut Repository) -> Result<()> {
|
||||
check_working_repo(repo)?;
|
||||
|
||||
// If our local branch exists, it must also have a commit on it. Therefore
|
||||
// we can apply stashes. Stow away our changes, rebase on remote, and then
|
||||
// reapply those changes.
|
||||
if repo
|
||||
.find_branch(&pc.config.repos.remote.branch, BranchType::Local)
|
||||
.is_ok()
|
||||
{
|
||||
return Ok(with_stash(pc, repo, |pc, repo| {
|
||||
Ok(local_rebase_remote(pc, repo)?)
|
||||
})?);
|
||||
}
|
||||
|
||||
// If our local branch does not exist yet, we are likely in an empty git
|
||||
// repository. In this case, we should just try to find the remote branch
|
||||
// and establish a copy locally of the same name.
|
||||
//
|
||||
// That said, there is a possibility our repository isn't empty. We also are
|
||||
// not necessarily able to stash changes and reapply them like we normally
|
||||
// would since its possible we do not have an initial commit yet. Generally
|
||||
// switching would be fine but its also possible the user has a file that
|
||||
// would be overwritten on change. For this reason, we just create an
|
||||
// initial commit for any existing files so the user can reference it later
|
||||
// if need be.
|
||||
if let Some(mut index) = index_with_all(repo)? {
|
||||
let index_oid = index.write_tree()?;
|
||||
let index_tree = repo.find_tree(index_oid)?;
|
||||
info!("Writing tree `{}`.", index_oid);
|
||||
|
||||
let signature = now_signature(pc)?;
|
||||
let message = "Save potentially conflicting files here.";
|
||||
// If we are on a current branch, there should exist a commit we
|
||||
// can just push onto. Otherwise let's create a new branch with the
|
||||
// saved contents.
|
||||
if let Some(parent_commit) = get_commit_at_head(repo) {
|
||||
repo.commit(
|
||||
Some("HEAD"),
|
||||
&signature,
|
||||
&signature,
|
||||
message,
|
||||
&index_tree,
|
||||
&[&parent_commit],
|
||||
)?;
|
||||
info!("Saved potentially conflicting files in new commit of HEAD.");
|
||||
} else {
|
||||
let temp_branch = temporary_branch_name(pc, repo)?;
|
||||
let refspec = format!("refs/heads/{}", &temp_branch);
|
||||
repo.commit(
|
||||
Some(&refspec),
|
||||
&signature,
|
||||
&signature,
|
||||
message,
|
||||
&index_tree,
|
||||
&[],
|
||||
)?;
|
||||
info!(
|
||||
"Saved potentially conflicting files on branch <yellow>{}</>",
|
||||
temp_branch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(local_from_remote(pc, repo)?)
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Index
|
||||
// ========================================
|
||||
|
||||
fn index_with_all(repo: &Repository) -> Result<Option<Index>> {
|
||||
let mut index = repo.index()?;
|
||||
index.add_all(["."].iter(), IndexAddOption::DEFAULT, None)?;
|
||||
let diff_stats = repo
|
||||
.diff_index_to_workdir(
|
||||
Some(&index),
|
||||
Some(
|
||||
DiffOptions::new()
|
||||
.include_untracked(true)
|
||||
.include_unreadable(true),
|
||||
),
|
||||
)?
|
||||
.stats()?;
|
||||
if diff_stats.files_changed() == 0
|
||||
&& diff_stats.insertions() == 0
|
||||
&& diff_stats.deletions() == 0
|
||||
{
|
||||
Ok(None)
|
||||
let has_diff = if let Some(commit) = get_commit_at_head(repo) {
|
||||
let diff_stats = repo
|
||||
.diff_tree_to_workdir_with_index(
|
||||
Some(&repo.find_tree(commit.tree_id())?),
|
||||
Some(
|
||||
DiffOptions::new()
|
||||
.include_untracked(true)
|
||||
.include_unreadable(true),
|
||||
),
|
||||
)?
|
||||
.stats()?;
|
||||
diff_stats.files_changed() != 0
|
||||
|| diff_stats.insertions() != 0
|
||||
|| diff_stats.deletions() != 0
|
||||
} else {
|
||||
!index.is_empty()
|
||||
};
|
||||
if has_diff {
|
||||
Ok(Some(index))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create or retrieve the remote specified within our configuration.
|
||||
///
|
||||
/// This method also configures the fetchspec for the remote, explicitly mapping
|
||||
/// the remote branch against our local one.
|
||||
///
|
||||
/// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
fn get_remote<'repo>(pc: &PathConfig, repo: &'repo Repository) -> Result<Remote<'repo>> {
|
||||
fn with_stash<T>(pc: &PathConfig, repo: &mut Repository, function: T) -> Result<()>
|
||||
where
|
||||
T: Fn(&PathConfig, &mut Repository) -> Result<()>,
|
||||
{
|
||||
let signature = now_signature(pc)?;
|
||||
let stash_oid = match repo.stash_save(
|
||||
&signature,
|
||||
"Temporary stash during pull",
|
||||
Some(StashFlags::INCLUDE_UNTRACKED),
|
||||
) {
|
||||
Ok(oid) => {
|
||||
info!("Stashing changes in `{}`.", oid);
|
||||
Some(oid)
|
||||
}
|
||||
Err(e) if e.class() == git2::ErrorClass::Stash && e.code() == git2::ErrorCode::NotFound => {
|
||||
None
|
||||
}
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
|
||||
function(pc, repo)?;
|
||||
|
||||
if let Some(oid) = stash_oid {
|
||||
// It is possible something else made changes to our stash while we were
|
||||
// rebasing. To be extra cautious, search for our specific stash
|
||||
// instance.
|
||||
let mut stash_index = None;
|
||||
repo.stash_foreach(|index, _message, each_oid| {
|
||||
if *each_oid == oid {
|
||||
stash_index = Some(index);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})?;
|
||||
if let Some(index) = stash_index {
|
||||
let mut checkout = git2::build::CheckoutBuilder::new();
|
||||
checkout.use_ours(true);
|
||||
|
||||
let mut apply_options = StashApplyOptions::new();
|
||||
apply_options.checkout_options(checkout);
|
||||
|
||||
repo.stash_apply(index, Some(&mut apply_options))?;
|
||||
info!("Reapplied stash `{}`.", oid);
|
||||
} else {
|
||||
warn!("Could not find stash `{}`. Ignoring.", oid);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Remote
|
||||
// ========================================
|
||||
|
||||
fn find_remote<'repo>(pc: &PathConfig, repo: &'repo Repository) -> Result<Remote<'repo>> {
|
||||
repo.remote_set_url(&pc.config.repos.remote.name, &pc.config.repos.remote.url)?;
|
||||
// If the remote already exists, this just updates the fetchspec. We could
|
||||
// go with "*" instead of {branch} for all remote branches, but choosing to
|
||||
// be precise..
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
repo.remote_add_fetch(
|
||||
&pc.config.repos.remote.name,
|
||||
// We could go with "*" instead of {branch} for all remote branches.
|
||||
&format!(
|
||||
"+refs/heads/{}:refs/remotes/{}",
|
||||
pc.config.repos.remote.branch,
|
||||
|
@ -261,119 +491,21 @@ fn get_remote<'repo>(pc: &PathConfig, repo: &'repo Repository) -> Result<Remote<
|
|||
Ok(repo.find_remote(&pc.config.repos.remote.name)?)
|
||||
}
|
||||
|
||||
pub fn push(pc: &PathConfig, repo: &mut Repository) -> Result<()> {
|
||||
// First pull to make sure there are no conflicts when we push our changes.
|
||||
// This will also perform validation and construct our local and remote
|
||||
// environment.
|
||||
let _local_branch = pull(&pc, &repo)?;
|
||||
|
||||
// The index corresponds to our staging area. We add all files and write out
|
||||
// to a tree. The resulting tree can be found using `git ls-tree <oid>`.
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
|
||||
let mut index = match index_add(&repo)? {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
info!("Nothing to push. Have you run `homesync stage`?");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
// Retrieve the latest commit before writing to the object database.
|
||||
let parent_commit = get_commit(&repo)?;
|
||||
let index_oid = index.write_tree()?;
|
||||
let index_tree = repo.find_tree(index_oid)?;
|
||||
info!("Writing index to tree `{}`.", index_oid);
|
||||
|
||||
// Commit our changes and push them to our remote.
|
||||
let refspec = format!("refs/heads/{}", &pc.config.repos.remote.branch);
|
||||
let signature = get_signature(&pc)?;
|
||||
repo.commit(
|
||||
Some(&refspec),
|
||||
&signature,
|
||||
&signature,
|
||||
// TODO(jrpotter): Come up with a more useful message.
|
||||
"homesync push",
|
||||
&index_tree,
|
||||
&[&parent_commit],
|
||||
)?;
|
||||
|
||||
let mut remote = get_remote(&pc, &repo)?;
|
||||
remote.connect(Direction::Push)?;
|
||||
remote.push(&[&format!("{r}:{r}", r = refspec)], None)?;
|
||||
info!(
|
||||
"Pushed changes to remote `{}`.",
|
||||
pc.config.repos.remote.tracking_branch(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pull<'repo>(pc: &PathConfig, repo: &'repo Repository) -> Result<Branch<'repo>> {
|
||||
validate_repo(&repo)?;
|
||||
|
||||
// TODO(jrpotter): Configure our remote to point to the same URL mentioned
|
||||
// in our config.
|
||||
// TODO(jrpotter): If changes are available, need to stage them and then
|
||||
// reapply.
|
||||
|
||||
// Establish our remote. If the remote already exists, re-configure it
|
||||
// blindly to point to the appropriate url. Our results should now exist
|
||||
// in a branch called `remotes/origin/<branch>`.
|
||||
// https://git-scm.com/book/it/v2/Git-Basics-Working-with-Remotes
|
||||
let mut remote = get_remote(&pc, &repo)?;
|
||||
let mut fetch_options = get_fetch_options(&pc)?;
|
||||
fn fetch_remote<'repo>(pc: &PathConfig, repo: &'repo Repository) -> Result<Remote<'repo>> {
|
||||
let mut remote = find_remote(pc, repo)?;
|
||||
let mut fetch_options = get_fetch_options(pc)?;
|
||||
remote.fetch(
|
||||
&[&pc.config.repos.remote.branch],
|
||||
Some(&mut fetch_options),
|
||||
None,
|
||||
)?;
|
||||
let remote_branch_name = pc.config.repos.remote.tracking_branch();
|
||||
let remote_branch = repo.find_branch(&remote_branch_name, BranchType::Remote)?;
|
||||
info!("Fetched remote branch `{}`.", &remote_branch_name);
|
||||
let tracking_branch = pc.config.repos.remote.tracking_branch();
|
||||
info!("Fetched remote branch `{}`.", &tracking_branch);
|
||||
|
||||
// There are two cases we need to consider:
|
||||
//
|
||||
// 1. Our local branch actually exists, in which case there are commits
|
||||
// available. These should be rebased relative to remote (our upstream).
|
||||
// 2. Our repository has been initialized in an empty state. The branch we
|
||||
// are interested in is unborn, so we can just copy the branch from remote.
|
||||
let remote_ref = repo.reference_to_annotated_commit(remote_branch.get())?;
|
||||
if let Ok(local_branch) = repo.find_branch(&pc.config.repos.remote.branch, BranchType::Local) {
|
||||
let local_ref = repo.reference_to_annotated_commit(local_branch.get())?;
|
||||
let signature = get_signature(&pc)?;
|
||||
repo.rebase(Some(&local_ref), Some(&remote_ref), None, None)?
|
||||
.finish(Some(&signature))?;
|
||||
info!("Rebased local branch onto `{}`.", remote_branch_name);
|
||||
Ok(local_branch)
|
||||
} else {
|
||||
let local_branch =
|
||||
repo.branch_from_annotated_commit(&pc.config.repos.remote.branch, &remote_ref, false)?;
|
||||
info!("Created new local branch from `{}`.", remote_branch_name);
|
||||
Ok(local_branch)
|
||||
}
|
||||
Ok(remote)
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Utility
|
||||
// ========================================
|
||||
|
||||
/// Verify the repository we are working in supports the operations we want to
|
||||
/// apply to it.
|
||||
fn validate_repo(repo: &Repository) -> Result<PathBuf> {
|
||||
Ok(repo.workdir().ok_or(Error::InvalidBareRepo)?.to_path_buf())
|
||||
}
|
||||
|
||||
/// Return the latest commit off of HEAD.
|
||||
fn get_commit(repo: &Repository) -> Result<Commit> {
|
||||
Ok(repo
|
||||
.head()?
|
||||
.resolve()?
|
||||
.peel(ObjectType::Commit)?
|
||||
.into_commit()
|
||||
.map_err(|_| git2::Error::from_str("Couldn't find commit"))?)
|
||||
}
|
||||
|
||||
/// Construct callbacks to supply authentication information on fetch/clone/etc.
|
||||
fn get_fetch_options(pc: &PathConfig) -> Result<FetchOptions> {
|
||||
fn get_remote_callbacks(pc: &PathConfig) -> Result<RemoteCallbacks> {
|
||||
let public_path = match &pc.config.ssh.public {
|
||||
Some(p) => Some(path::resolve(p)?),
|
||||
None => None,
|
||||
|
@ -390,12 +522,63 @@ fn get_fetch_options(pc: &PathConfig) -> Result<FetchOptions> {
|
|||
)
|
||||
});
|
||||
|
||||
Ok(callbacks)
|
||||
}
|
||||
|
||||
fn get_fetch_options(pc: &PathConfig) -> Result<FetchOptions> {
|
||||
let callbacks = get_remote_callbacks(pc)?;
|
||||
let mut fetch_options = FetchOptions::new();
|
||||
fetch_options.remote_callbacks(callbacks);
|
||||
Ok(fetch_options)
|
||||
}
|
||||
|
||||
/// Generate a new signature at the current time.
|
||||
fn get_signature(pc: &PathConfig) -> Result<Signature> {
|
||||
fn get_push_options(pc: &PathConfig) -> Result<PushOptions> {
|
||||
let callbacks = get_remote_callbacks(pc)?;
|
||||
let mut push_options = PushOptions::new();
|
||||
push_options.remote_callbacks(callbacks);
|
||||
Ok(push_options)
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Miscellaneous
|
||||
// ========================================
|
||||
|
||||
fn check_working_repo(repo: &Repository) -> Result<PathBuf> {
|
||||
Ok(repo.workdir().ok_or(Error::InvalidBareRepo)?.to_path_buf())
|
||||
}
|
||||
|
||||
fn get_commit_at_head(repo: &Repository) -> Option<Commit> {
|
||||
let peel = || -> Result<Commit> {
|
||||
Ok(repo
|
||||
.head()?
|
||||
.resolve()?
|
||||
.peel(ObjectType::Commit)?
|
||||
.into_commit()
|
||||
.map_err(|_| git2::Error::from_str("Couldn't find commit"))?)
|
||||
};
|
||||
peel().ok()
|
||||
}
|
||||
|
||||
fn now_signature(pc: &PathConfig) -> Result<Signature> {
|
||||
Ok(Signature::now(&pc.config.user.name, &pc.config.user.email)?)
|
||||
}
|
||||
|
||||
fn temporary_branch_name(pc: &PathConfig, repo: &Repository) -> Result<String> {
|
||||
let mut branch_names = HashSet::new();
|
||||
for b in repo.branches(Some(BranchType::Local))? {
|
||||
if let Ok((branch, _branch_type)) = b {
|
||||
if let Some(name) = branch.name()? {
|
||||
branch_names.insert(name.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut count = 1;
|
||||
let mut temp_name = format!("{}-tmp", &pc.config.repos.remote.branch);
|
||||
while branch_names.contains(&temp_name) {
|
||||
temp_name = format!("{}-tmp-{}", &pc.config.repos.remote.branch, count);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
Ok(temp_name)
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ pub fn run_push(config: PathConfig) -> Result {
|
|||
}
|
||||
|
||||
pub fn run_pull(config: PathConfig) -> Result {
|
||||
let repo = git::init(&config)?;
|
||||
git::pull(&config, &repo)?;
|
||||
let mut repo = git::init(&config)?;
|
||||
git::pull(&config, &mut repo)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue