termesh-git 0.1.3

Internal component of a terminal-native, agent-first IDE.
Documentation
use std::fs;
use std::path::Path;
use std::process::Command;

use termesh_core::GitOperation;
use termesh_git::{GitService, RealGitService};

fn git(root: &Path, args: &[&str]) {
    let output = Command::new("git").current_dir(root).args(args).output().unwrap();
    assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
}

fn git_stdout(root: &Path, args: &[&str]) -> String {
    let output = Command::new("git").current_dir(root).args(args).output().unwrap();
    assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
    String::from_utf8_lossy(&output.stdout).into_owned()
}

#[test]
fn commit_consumes_only_the_explicit_index() {
    if Command::new("git").arg("--version").output().is_err() {
        return;
    }
    let stamp =
        std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
    let root = std::env::temp_dir().join(format!("termesh-git-{stamp}"));
    fs::create_dir_all(&root).unwrap();
    git(&root, &["init", "-q"]);
    git(&root, &["config", "user.name", "Termesh Test"]);
    git(&root, &["config", "user.email", "termesh@example.invalid"]);
    fs::write(root.join("staged.txt"), "base\n").unwrap();
    fs::write(root.join("unstaged.txt"), "base\n").unwrap();
    git(&root, &["add", "--", "staged.txt", "unstaged.txt"]);
    git(&root, &["commit", "-qm", "initial"]);
    fs::write(root.join("staged.txt"), "staged change\n").unwrap();
    fs::write(root.join("unstaged.txt"), "must survive\n").unwrap();
    git(&root, &["add", "--", "staged.txt"]);

    let mut service = RealGitService::new();
    let before = service.snapshot(&root).unwrap();
    assert!(before.files.iter().any(|file| file.index.is_some()));
    assert!(before.files.iter().any(|file| file.worktree.is_some()));

    service.execute(&root, &GitOperation::Commit { message: "staged only".into() }).unwrap();

    let after = service.snapshot(&root).unwrap();
    assert!(after.files.iter().all(|file| file.index.is_none()));
    assert!(after.files.iter().any(|file| file.worktree.is_some()));
    assert_eq!(fs::read_to_string(root.join("unstaged.txt")).unwrap(), "must survive\n");
    let _ = fs::remove_dir_all(root);
}

#[test]
fn push_without_upstream_publishes_branch_to_origin() {
    if Command::new("git").arg("--version").output().is_err() {
        return;
    }
    let stamp =
        std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
    let base = std::env::temp_dir().join(format!("termesh-git-push-{stamp}"));
    let root = base.join("working");
    let remote = base.join("origin.git");
    fs::create_dir_all(&root).unwrap();
    fs::create_dir_all(&remote).unwrap();
    git(&remote, &["init", "--bare", "-q"]);
    git(&root, &["init", "-q", "-b", "main"]);
    git(&root, &["config", "user.name", "Termesh Test"]);
    git(&root, &["config", "user.email", "termesh@example.invalid"]);
    fs::write(root.join("published.txt"), "published\n").unwrap();
    git(&root, &["add", "--", "published.txt"]);
    git(&root, &["commit", "-qm", "publish me"]);
    git(&root, &["switch", "-qc", "feature/first-push"]);
    git(&root, &["remote", "add", "origin", remote.to_str().unwrap()]);

    let mut service = RealGitService::new();
    service.execute(&root, &GitOperation::Push).unwrap();

    let upstream =
        git_stdout(&root, &["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
    assert_eq!(upstream.trim(), "origin/feature/first-push");
    git(&remote, &["rev-parse", "--verify", "refs/heads/feature/first-push"]);
    let _ = fs::remove_dir_all(base);
}