Skip to main content

joy_core/
git_ops.rs

1// Copyright (c) 2026 Joydev GmbH (joydev.com)
2// SPDX-License-Identifier: MIT
3
4//! Automatic git operations triggered by Joy file writes.
5//! All operations are best-effort: failures print a warning but never
6//! abort the Joy command.
7
8use std::path::Path;
9
10use crate::model::config::AutoGit;
11use crate::store;
12use crate::vcs::default_vcs;
13
14/// Read the configured auto-git level.
15pub fn auto_git_level() -> AutoGit {
16    store::load_config().workflow.auto_git
17}
18
19/// Stage the given paths if auto-git >= Add.
20/// Paths are relative to the project root.
21/// Errors are printed as warnings and swallowed.
22pub fn auto_git_add(root: &Path, paths: &[&str]) {
23    let level = auto_git_level();
24    if !level.should_add() || paths.is_empty() {
25        return;
26    }
27    let vcs = default_vcs();
28    if let Err(e) = vcs.add(root, paths) {
29        eprintln!("Warning: auto-git add failed: {e}");
30    }
31}
32
33/// After a mutating command completes, commit and optionally push
34/// if auto-git >= Commit.
35///
36/// `summary` is the commit subject line (e.g. "add JOY-005D Auto-add...").
37/// `identity` is the Joy identity string for Co-Authored-By.
38pub fn auto_git_post_command(root: &Path, summary: &str, identity: &str) {
39    let level = auto_git_level();
40    if !level.should_commit() {
41        return;
42    }
43
44    let vcs = default_vcs();
45
46    let message = format!("joy: {summary}\n\nCo-Authored-By: {identity}");
47    if let Err(e) = vcs.commit(root, &message) {
48        let err = e.to_string();
49        // "nothing to commit" is not an error worth warning about
50        if !err.contains("nothing to commit") {
51            eprintln!("Warning: auto-git commit failed: {e}");
52        }
53        return;
54    }
55
56    if level.should_push() {
57        let remote = vcs.default_remote(root).unwrap_or_else(|_| "origin".into());
58        if let Err(e) = vcs.push(root, &remote) {
59            eprintln!("Warning: auto-git push failed: {e}");
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn auto_git_level_returns_default() {
70        // Outside a project root, load_config returns default (Add)
71        let level = auto_git_level();
72        assert_eq!(level, AutoGit::Add);
73    }
74}