diff --git a/CHANGELOG.md b/CHANGELOG.md index 322bfbe..d9d354d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Added +- Option to use `git` cli in `sync` subcommand. This is now the default (#27) + ## [0.4.0] - 2025-03-01 ### Added diff --git a/src/cmd_args.rs b/src/cmd_args.rs index 1c3eb3c..82debe6 100644 --- a/src/cmd_args.rs +++ b/src/cmd_args.rs @@ -1,7 +1,7 @@ //! CLI arguments -use crate::path; use crate::PathBuf; +use crate::path; use clap::Args; use clap::{Parser, Subcommand}; use clap_verbosity_flag::Verbosity; @@ -63,6 +63,9 @@ pub(crate) enum Commands { Sync { /// Remote name to sync. remote_name: Option, + /// Use custom git implementation. + #[arg(short, long)] + use_libgit2: bool, /// Whether to use ssh-agent #[arg(long)] use_sshagent: bool, diff --git a/src/cmd_init.rs b/src/cmd_init.rs index 7207202..c9d6d8a 100644 --- a/src/cmd_init.rs +++ b/src/cmd_init.rs @@ -2,11 +2,13 @@ //! Initialize xdbm for the device. use crate::backups::Backups; -use crate::storages::{Storages, STORAGESFILE}; +use crate::storages::{STORAGESFILE, Storages}; use crate::{ - add_and_commit, backups, full_status, get_devices, write_devices, Device, DEVICESFILE, + DEVICESFILE, Device, add_and_commit, backups, + devices::{get_devices, write_devices}, + full_status, }; -use anyhow::{anyhow, Context, Ok, Result}; +use anyhow::{Context, Ok, Result, anyhow}; use core::panic; use git2::{Cred, RemoteCallbacks, Repository}; use inquire::Password; diff --git a/src/cmd_sync.rs b/src/cmd_sync.rs index 9611c7a..d6186f5 100644 --- a/src/cmd_sync.rs +++ b/src/cmd_sync.rs @@ -1,16 +1,77 @@ use std::{ io::{self, Write}, path::{Path, PathBuf}, + process, }; -use anyhow::{anyhow, Context, Result}; -use git2::{build::CheckoutBuilder, Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository}; +use anyhow::{Context, Result, anyhow}; +use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository, build::CheckoutBuilder}; pub(crate) fn cmd_sync( config_dir: &PathBuf, remote_name: Option, use_sshagent: bool, ssh_key: Option, + use_libgit2: bool, +) -> Result<()> { + if use_libgit2 { + cmd_sync_custom(config_dir, remote_name, use_sshagent, ssh_key) + } else { + cmd_sync_cl(config_dir, remote_name, ssh_key) + } +} + +fn cmd_sync_cl( + config_dir: &PathBuf, + remote_name: Option, + ssh_key: Option, +) -> Result<()> { + info!("cmd_sync (command line version)"); + + trace!("pull"); + let args = |cmd| { + let mut args = vec![cmd]; + if let Some(ref remote_name) = remote_name { + args.push(remote_name.clone()); + } + if let Some(ref ssh_key) = ssh_key { + args.push("-i".to_string()); + args.push(ssh_key.to_str().unwrap().to_owned()); + } + args + }; + let git_pull_result = process::Command::new("git") + .args(args("pull".to_owned())) + .current_dir(config_dir) + .status() + .context("error while executing git pull")? + .success(); + if git_pull_result { + eprintln!("git pull completed"); + } else { + return Err(anyhow!("failed to complete git pull")); + } + + trace!("push"); + let git_push_result = process::Command::new("git") + .args(args("push".to_owned())) + .current_dir(config_dir) + .status() + .context("error while executing git push")? + .success(); + if git_push_result { + eprintln!("git push completed"); + } else { + return Err(anyhow!("failed to complete git push")); + } + Ok(()) +} + +fn cmd_sync_custom( + config_dir: &PathBuf, + remote_name: Option, + use_sshagent: bool, + ssh_key: Option, ) -> Result<()> { info!("cmd_sync"); let repo = Repository::open(config_dir)?; @@ -81,13 +142,13 @@ where }) .transfer_progress(|progress| { if progress.received_objects() == progress.total_objects() { - print!( + eprint!( "Resolving deltas {}/{}\r", progress.indexed_deltas(), progress.total_deltas() ); } else { - print!( + eprint!( "Received {}/{} objects ({}) in {} bytes\r", progress.received_objects(), progress.total_objects(), @@ -95,7 +156,7 @@ where progress.received_bytes(), ); } - io::stdout().flush().unwrap(); + io::stderr().flush().unwrap(); true }) .sideband_progress(|text| { @@ -149,7 +210,7 @@ fn pull( .context("Failed to fetch (pull)")?; let stats = remote.stats(); if stats.local_objects() > 0 { - println!( + eprintln!( "\rReceived {}/{} objects in {} bytes (used {} local objects)", stats.indexed_objects(), stats.total_objects(), @@ -157,7 +218,7 @@ fn pull( stats.local_objects(), ); } else { - println!( + eprintln!( "\rReceived {}/{} objects in {} bytes", stats.indexed_objects(), stats.total_objects(), @@ -198,7 +259,7 @@ fn pull( None => String::from_utf8_lossy(ref_remote.name_bytes()).to_string(), }; let msg = format!("Fast-Forward: Setting {} to id: {}", name, fetch_head.id()); - println!("{}", msg); + eprintln!("{}", msg); ref_remote .set_target(fetch_head.id(), &msg) .context("failed to set target")?; @@ -238,7 +299,7 @@ fn push( ssh_key: Option<&PathBuf>, ) -> Result<()> { debug!("push"); - let callbacks = remote_callback(&use_sshagent, ssh_key); + let callbacks = remote_callback(use_sshagent, ssh_key); let mut push_options = PushOptions::new(); push_options.remote_callbacks(callbacks); let num_push_refspecs = remote diff --git a/src/main.rs b/src/main.rs index 41aab33..7c8d47e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use std::path::{self, PathBuf}; use storages::Storages; use crate::cmd_args::{BackupSubCommands, Cli, Commands, StorageCommands}; -use devices::{Device, DEVICESFILE, *}; +use devices::{DEVICESFILE, Device}; mod backups; mod cmd_args; @@ -94,9 +94,10 @@ fn main() -> Result<()> { } Commands::Sync { remote_name, + use_libgit2, use_sshagent, ssh_key, - } => cmd_sync::cmd_sync(&config_dir, remote_name, use_sshagent, ssh_key)?, + } => cmd_sync::cmd_sync(&config_dir, remote_name, use_sshagent, ssh_key, use_libgit2)?, Commands::Status { path, storage, diff --git a/tests/cli.rs b/tests/cli.rs index b282410..4582915 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -5,8 +5,8 @@ mod integrated_test { path, }; - use anyhow::{anyhow, Context, Ok, Result}; - use assert_cmd::{assert::OutputAssertExt, Command}; + use anyhow::{Context, Ok, Result, anyhow}; + use assert_cmd::{Command, assert::OutputAssertExt}; use git2::Repository; use log::{debug, trace}; use predicates::{boolean::PredicateBooleanExt, prelude::predicate}; @@ -73,13 +73,22 @@ mod integrated_test { Ok(()) } - fn run_sync_cmd(config_dir: &path::Path) -> Result<()> { - Command::cargo_bin("xdbm")? - .arg("-c") - .arg(config_dir) - .args(["sync", "-vvvv"]) - .assert() - .success(); + fn run_sync_cmd(config_dir: &path::Path, use_cl: bool) -> Result<()> { + if use_cl { + Command::cargo_bin("xdbm")? + .arg("-c") + .arg(config_dir) + .args(["sync", "-vvvv"]) + .assert() + .success(); + } else { + Command::cargo_bin("xdbm")? + .arg("-c") + .arg(config_dir) + .args(["sync", "-vvvv", "-u"]) + .assert() + .success(); + } Ok(()) } @@ -329,6 +338,7 @@ mod integrated_test { .arg(config_dir_2.path()) .arg("sync") .arg("-vvvv") + .arg("-u") .assert() .success() .stderr(predicate::str::contains("successfully pushed")); @@ -391,8 +401,8 @@ mod integrated_test { std::fs::read_to_string(config_dir_1.join("storages.yml"))?.contains("parent: gdrive1") ); - run_sync_cmd(&config_dir_1)?; - run_sync_cmd(&config_dir_2)?; + run_sync_cmd(&config_dir_1, false)?; + run_sync_cmd(&config_dir_2, false)?; // bind // @@ -606,8 +616,8 @@ mod integrated_test { .and(predicate::str::contains("foodoc").not()), ); - run_sync_cmd(&config_dir_2)?; - run_sync_cmd(&config_dir_1)?; + run_sync_cmd(&config_dir_2, true)?; + run_sync_cmd(&config_dir_1, true)?; // bind // @@ -722,8 +732,8 @@ mod integrated_test { .assert() .success(); - run_sync_cmd(&config_dir_1)?; - run_sync_cmd(&config_dir_2)?; + run_sync_cmd(&config_dir_1, false)?; + run_sync_cmd(&config_dir_2, false)?; // backup add //