1use std::io;
7use std::path::Path;
8use std::process::Command;
9
10pub mod aliases;
11pub mod attr;
12pub mod cat_file;
13pub mod config;
14pub mod diff_index;
15pub mod endpoint;
16pub mod extension;
17pub mod fetch_prune;
18pub mod http_options;
19pub mod path;
20pub mod pktline;
21pub mod refs;
22pub mod rev_list;
23pub mod scanner;
24
25pub use attr::AttrSet;
26pub use cat_file::{BlobContent, CatFileBatch, CatFileBatchCheck, CatFileHeader};
27pub use config::ConfigScope;
28pub use diff_index::{DiffEntry, diff_index};
29pub use endpoint::{
30 EndpointError, EndpointInfo, SshInfo, derive_lfs_url, endpoint_for_remote, looks_like_url,
31 parse_ssh_url, remote_url, resolve_endpoint,
32};
33pub use extension::{ExtensionConfig, list_extensions};
34pub use fetch_prune::FetchPruneConfig;
35pub use http_options::{HttpOptions, extra_headers_for, lfs_url_bool};
36pub use path::{git_common_dir, git_dir, lfs_alternate_dirs, lfs_dir, work_tree_root};
37pub use refs::{RecentRef, RefKind, WorktreeEntry, recent_branches, worktrees};
38pub use rev_list::{RevListEntry, rev_list, rev_list_with_args};
39pub use scanner::{
40 PointerEntry, TreeBlob, scan_index_lfs, scan_index_pointers, scan_pointers,
41 scan_pointers_with_args, scan_previous_versions, scan_stashed, scan_tree, scan_tree_blobs,
42};
43
44#[derive(Debug, thiserror::Error)]
45pub enum Error {
46 #[error("io error invoking git: {0}")]
47 Io(#[from] io::Error),
48 #[error("git: {0}")]
49 Failed(String),
50}
51
52pub(crate) fn run_git(cwd: &Path, args: &[&str]) -> Result<String, Error> {
54 let out = Command::new("git").arg("-C").arg(cwd).args(args).output()?;
55 if !out.status.success() {
56 return Err(Error::Failed(
57 String::from_utf8_lossy(&out.stderr).trim().to_owned(),
58 ));
59 }
60 Ok(String::from_utf8(out.stdout)
61 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
62 .trim()
63 .to_owned())
64}
65
66#[cfg(test)]
67pub(crate) mod tests {
68 pub mod commit_helper {
72 use std::path::Path;
73 use std::process::Command;
74
75 use tempfile::TempDir;
76
77 pub fn init_repo() -> TempDir {
80 for var in ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"] {
88 assert!(
89 std::env::var_os(var).is_none(),
90 "{var} is set in the test process — git subprocesses \
91 will ignore the per-test tempdir. Run via \
92 `just pre-commit` (which strips it) or \
93 `env -u {var} cargo test`."
94 );
95 }
96 let tmp = TempDir::new().unwrap();
97 run(tmp.path(), &["init", "--quiet", "--initial-branch=main"]);
98 run(tmp.path(), &["config", "user.email", "test@example.com"]);
99 run(tmp.path(), &["config", "user.name", "test"]);
100 run(tmp.path(), &["config", "commit.gpgsign", "false"]);
103 tmp
104 }
105
106 pub fn commit_file(repo: &TempDir, path: &str, content: &[u8]) {
110 std::fs::write(repo.path().join(path), content).unwrap();
111 run(repo.path(), &["add", path]);
112 run(
113 repo.path(),
114 &["commit", "--quiet", "-m", &format!("add {path}")],
115 );
116 }
117
118 pub fn head_oid(repo: &TempDir) -> String {
120 let out = Command::new("git")
121 .arg("-C")
122 .arg(repo.path())
123 .args(["rev-parse", "HEAD"])
124 .output()
125 .unwrap();
126 assert!(out.status.success());
127 String::from_utf8_lossy(&out.stdout).trim().to_owned()
128 }
129
130 fn run(cwd: &Path, args: &[&str]) {
131 let status = Command::new("git")
132 .arg("-C")
133 .arg(cwd)
134 .args(args)
135 .status()
136 .unwrap();
137 assert!(status.success(), "git {args:?} failed");
138 }
139 }
140}