mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-18 09:25:14 +02:00
Scope Git watching and status to the sync directory (#503)
This commit is contained in:
@@ -1,11 +1,39 @@
|
||||
use crate::binary::new_binary_command;
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::repository::open_repo;
|
||||
use crate::status::repo_relative_dir;
|
||||
use log::info;
|
||||
use std::path::Path;
|
||||
|
||||
/// Commit the staged changes within `dir` (their current worktree content,
|
||||
/// matching what the commit dialog displays). Scoping the commit to the sync
|
||||
/// directory means files staged outside of it (e.g. elsewhere in a containing
|
||||
/// monorepo) are never swept into a Yaak commit — they stay staged for the
|
||||
/// user's own next commit.
|
||||
pub async fn git_commit(dir: &Path, message: &str) -> crate::error::Result<()> {
|
||||
let out =
|
||||
new_binary_command(dir).await?.args(["commit", "--message", message]).output().await?;
|
||||
// Run git from the repo root: command-line pathspecs resolve relative to
|
||||
// the working directory, and staged paths are repo-root-relative
|
||||
let (workdir, rela_dir) = {
|
||||
let repo = open_repo(dir)?;
|
||||
let workdir = repo
|
||||
.workdir()
|
||||
.ok_or_else(|| GenericError("Repository has no worktree".to_string()))?
|
||||
.to_path_buf();
|
||||
(workdir, repo_relative_dir(&repo, dir))
|
||||
};
|
||||
|
||||
let staged = staged_files(&workdir, rela_dir.as_deref()).await?;
|
||||
if staged.is_empty() {
|
||||
return Err(GenericError("No staged changes to commit".to_string()));
|
||||
}
|
||||
|
||||
let mut cmd = new_binary_command(&workdir).await?;
|
||||
// --literal-pathspecs: the staged paths are exact files, never patterns
|
||||
cmd.arg("--literal-pathspecs");
|
||||
cmd.args(["commit", "--message", message, "--"]);
|
||||
cmd.args(staged);
|
||||
|
||||
let out = cmd.output().await?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
@@ -19,3 +47,60 @@ pub async fn git_commit(dir: &Path, message: &str) -> crate::error::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Repo-relative paths of staged changes, limited to `rela_dir` when given.
|
||||
/// Must be run from the repo root so the pathspec resolves correctly. Uses
|
||||
/// -z for NUL separation so paths with special characters come through
|
||||
/// unquoted, --no-renames so staged renames list both sides, and
|
||||
/// --literal-pathspecs so a scope dir with glob characters isn't a pattern
|
||||
async fn staged_files(workdir: &Path, rela_dir: Option<&str>) -> crate::error::Result<Vec<String>> {
|
||||
let mut cmd = new_binary_command(workdir).await?;
|
||||
cmd.arg("--literal-pathspecs");
|
||||
cmd.args(["diff", "--cached", "--name-only", "--no-renames", "-z"]);
|
||||
if let Some(rela_dir) = rela_dir {
|
||||
cmd.args(["--", rela_dir]);
|
||||
}
|
||||
let out = cmd.output().await?;
|
||||
|
||||
if !out.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
return Err(GenericError(format!("Failed to list staged files: {}", stderr)));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&out.stdout)
|
||||
.split('\0')
|
||||
.filter(|p| !p.is_empty())
|
||||
.map(String::from)
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_staged_files_scoped() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let repo = git2::Repository::init(tmp.path()).unwrap();
|
||||
|
||||
// Scope dir name doubles as a glob pattern; sync1 is its glob decoy
|
||||
let sync_dir = tmp.path().join("sync[1]");
|
||||
std::fs::create_dir(&sync_dir).unwrap();
|
||||
std::fs::write(sync_dir.join("yaak.req_1.yaml"), "inside").unwrap();
|
||||
std::fs::create_dir(tmp.path().join("sync1")).unwrap();
|
||||
std::fs::write(tmp.path().join("sync1").join("decoy.txt"), "decoy").unwrap();
|
||||
std::fs::write(tmp.path().join("outside.txt"), "outside").unwrap();
|
||||
|
||||
let mut index = repo.index().unwrap();
|
||||
index.add_path(Path::new("sync[1]/yaak.req_1.yaml")).unwrap();
|
||||
index.add_path(Path::new("sync1/decoy.txt")).unwrap();
|
||||
index.add_path(Path::new("outside.txt")).unwrap();
|
||||
index.write().unwrap();
|
||||
|
||||
let scoped = staged_files(tmp.path(), Some("sync[1]")).await.unwrap();
|
||||
assert_eq!(scoped, vec!["sync[1]/yaak.req_1.yaml"]);
|
||||
|
||||
let all = staged_files(tmp.path(), None).await.unwrap();
|
||||
assert_eq!(all.len(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user