git-sync-rs 0.7.7

Automatic git repository synchronization with file watching
Documentation
mod common;

use anyhow::Result;
use common::TestRepoSetup;
use git_sync_rs::{RepositorySynchronizer, SyncConfig, SyncError};
use std::fs;
use std::os::unix::fs::PermissionsExt;

fn install_failing_pre_commit_hook(repo_path: &std::path::Path) -> Result<()> {
    let hook_path = repo_path.join(".git/hooks/pre-commit");
    fs::write(
        &hook_path,
        "#!/bin/sh\n\necho 'pre-commit failed from test' >&2\nexit 1\n",
    )?;
    let mut perms = fs::metadata(&hook_path)?.permissions();
    perms.set_mode(0o755);
    fs::set_permissions(&hook_path, perms)?;
    Ok(())
}

fn sync_config(skip_hooks: bool) -> SyncConfig {
    SyncConfig {
        sync_new_files: true,
        skip_hooks,
        commit_message: Some("Hook behavior test".to_string()),
        remote_name: "origin".to_string(),
        branch_name: "master".to_string(),
        conflict_branch: false,
        target_branch: None,
    }
}

#[test]
fn failing_pre_commit_hook_blocks_sync_when_skip_hooks_is_false() -> Result<()> {
    let setup = TestRepoSetup::new()?;
    setup.commit_file("tracked.txt", "v1\n", "initial")?;
    setup.push()?;
    install_failing_pre_commit_hook(&setup.local_path)?;

    fs::write(setup.local_path.join("tracked.txt"), "v2\n")?;

    let mut synchronizer =
        RepositorySynchronizer::new_with_detected_branch(&setup.local_path, sync_config(false))?;
    let result = synchronizer.sync(false);

    assert!(
        matches!(result, Err(SyncError::HookRejected { .. })),
        "Expected hook rejection when skip_hooks=false, got: {result:?}"
    );

    Ok(())
}

#[test]
fn failing_pre_commit_hook_is_bypassed_when_skip_hooks_is_true() -> Result<()> {
    let setup = TestRepoSetup::new()?;
    setup.commit_file("tracked.txt", "v1\n", "initial")?;
    setup.push()?;
    install_failing_pre_commit_hook(&setup.local_path)?;
    let second_clone = setup.create_second_clone("second")?;

    fs::write(setup.local_path.join("tracked.txt"), "v2\n")?;

    let mut synchronizer =
        RepositorySynchronizer::new_with_detected_branch(&setup.local_path, sync_config(true))?;
    synchronizer.sync(false)?;

    setup.pull_in(&second_clone)?;
    setup.assert_file_content_in(&second_clone, "tracked.txt", "v2\n")?;

    Ok(())
}