Skip to main content

anodizer_core/git/
mod.rs

1use anyhow::{Result, bail};
2use std::path::Path;
3use std::process::Command;
4
5mod commits;
6mod detect;
7mod github_api;
8mod remote;
9mod semver;
10mod snapshot_sde;
11mod status;
12mod tags;
13pub mod worktree;
14
15#[cfg(test)]
16mod tests;
17
18pub use commits::{
19    Commit, SHORT_COMMIT_LEN, add_path_in, commit_in, get_all_commits, get_all_commits_in,
20    get_all_commits_paths, get_all_commits_paths_in, get_commit_messages_between,
21    get_commit_messages_between_in, get_commit_messages_between_path,
22    get_commit_messages_between_path_in, get_commits_between, get_commits_between_in,
23    get_commits_between_paths, get_commits_between_paths_in, get_current_branch,
24    get_current_branch_in, get_head_commit, get_head_commit_in, get_last_commit_messages,
25    get_last_commit_messages_in, get_last_commit_messages_path, get_last_commit_messages_path_in,
26    get_short_commit, get_short_commit_in, has_changes_since, has_changes_since_in,
27    has_commits_since_tag, has_commits_since_tag_in, head_commit_hash_in, head_commit_timestamp_in,
28    log_subjects_for_range, paths_changed_since_tag, paths_changed_since_tag_in, short_commit_str,
29    stage_and_commit, stage_and_commit_in,
30};
31pub use detect::{GitInfo, detect_git_info, detect_git_info_in};
32pub use github_api::{
33    create_tag_via_github_api, create_tag_via_github_api_in, gh_api_get, gh_api_get_paginated,
34    gh_api_get_paginated_with_binary, gh_api_get_with_binary,
35};
36pub use remote::{
37    detect_github_repo, detect_github_repo_in, detect_owner_repo, detect_owner_repo_in,
38    parse_github_remote, parse_remote_owner_repo,
39};
40pub use semver::{SemVer, parse_semver, parse_semver_tag};
41pub use snapshot_sde::resolve_snapshot_sde;
42pub use status::{
43    check_git_available, git_status_porcelain, git_status_porcelain_in, is_git_dirty,
44    is_git_dirty_in, is_git_repo, is_git_repo_in, is_shallow_clone, is_shallow_clone_in,
45    local_git_user_email, local_git_user_email_in, local_git_user_name, local_git_user_name_in,
46};
47pub use tags::{
48    create_and_push_tag, create_and_push_tag_in, extract_tag_prefix, find_latest_tag_matching,
49    find_latest_tag_matching_in, find_latest_tag_matching_with_prefix,
50    find_latest_tag_matching_with_prefix_in, find_previous_tag, find_previous_tag_in,
51    find_previous_tag_with_prefix, find_previous_tag_with_prefix_in, get_all_semver_tags,
52    get_all_semver_tags_in, get_branch_semver_tags, get_branch_semver_tags_in, get_first_commit,
53    get_first_commit_in, has_version_placeholder, head_is_at_tag, list_tags_with_prefix,
54    render_ignore_patterns, strip_monorepo_prefix, tag_points_at_head, tag_points_at_head_in,
55};
56pub use worktree::Worktree;
57
58/// Run `git` in `cwd` and return stdout, trimmed.
59///
60/// Shared low-level git invocation wrapper. Path-taking so callers
61/// that don't own the process cwd — notably tests against a `tempfile::tempdir()`
62/// fixture repo — can drive git without mutating the process-wide cwd
63/// (which would race every other parallel test). The no-arg public wrappers
64/// in sibling submodules delegate here with `std::env::current_dir()`.
65///
66/// On non-zero exit the stderr is passed through
67/// [`crate::redact::redact_process_env`] before interpolation, so any
68/// token-bearing remote URL git might echo (e.g.
69/// `https://ghp_xxx@github.com/...` produced by an `extraHeader` config
70/// leak) is scrubbed in the bail message.
71pub(crate) fn git_output_in(cwd: &Path, args: &[&str]) -> Result<String> {
72    let output = Command::new("git").current_dir(cwd).args(args).output()?;
73    if !output.status.success() {
74        let stderr_raw = String::from_utf8_lossy(&output.stderr);
75        let raw = format!("git {} failed: {}", args.join(" "), stderr_raw.trim());
76        bail!("{}", crate::redact::redact_process_env(&raw));
77    }
78    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
79}