Skip to main content

git_spawn/
workflow.rs

1//! Common multi-step compositions, one call apiece.
2//!
3//! Reached through [`Repository::workflow`], which returns a [`WorkflowOps`]
4//! handle. Each method bundles two or three raw commands into a single async
5//! call — the same things you'd usually script by hand.
6//!
7//! ```no_run
8//! # async fn ex() -> git_spawn::Result<()> {
9//! use git_spawn::Repository;
10//!
11//! let repo = Repository::open("/path/to/repo")?;
12//!
13//! // Start a new feature branch off main.
14//! repo.workflow().feature_branch("feature/widget", "main").await?;
15//!
16//! // Stage everything dirty and commit it.
17//! repo.workflow().commit_all("wip: widget").await?;
18//!
19//! // Rebase the current branch onto its upstream.
20//! repo.workflow().sync().await?;
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! Workflow methods don't add new behavior — they just spare callers from
26//! chaining the existing builders by hand. If you need finer control (e.g.
27//! `add -u` instead of `add -A`), reach for the raw commands directly.
28
29use crate::command::GitCommand;
30use crate::command::add::AddCommand;
31use crate::command::checkout::CheckoutCommand;
32use crate::command::commit::CommitCommand;
33use crate::command::merge::MergeCommand;
34use crate::command::pull::PullCommand;
35use crate::error::Result;
36use crate::repo::Repository;
37
38/// High-level workflow helpers, scoped to a [`Repository`].
39#[derive(Debug)]
40pub struct WorkflowOps<'a> {
41    repo: &'a Repository,
42}
43
44impl<'a> WorkflowOps<'a> {
45    /// Create a new branch `name` starting at `base` and switch to it.
46    ///
47    /// Equivalent to `git checkout -b <name> <base>`. Errors if a branch
48    /// named `name` already exists.
49    pub async fn feature_branch(
50        &self,
51        name: impl Into<String>,
52        base: impl Into<String>,
53    ) -> Result<()> {
54        let mut cmd = CheckoutCommand::new();
55        cmd.create(name).target(base);
56        cmd.current_dir(self.repo.path());
57        cmd.execute().await?;
58        Ok(())
59    }
60
61    /// Stage every change in the working tree and commit them with `message`.
62    ///
63    /// Equivalent to `git add -A` followed by `git commit -m <message>`.
64    /// Errors if there is nothing to commit.
65    pub async fn commit_all(&self, message: impl Into<String>) -> Result<()> {
66        let mut add = AddCommand::new();
67        add.all();
68        add.current_dir(self.repo.path());
69        add.execute().await?;
70
71        let mut commit = CommitCommand::new();
72        commit.message(message);
73        commit.current_dir(self.repo.path());
74        commit.execute().await?;
75        Ok(())
76    }
77
78    /// Bring the current branch up to date with its upstream via rebase.
79    ///
80    /// Equivalent to `git pull --rebase`. Errors if no upstream is configured
81    /// or the rebase has conflicts.
82    pub async fn sync(&self) -> Result<()> {
83        let mut cmd = PullCommand::new();
84        cmd.rebase();
85        cmd.current_dir(self.repo.path());
86        cmd.execute().await?;
87        Ok(())
88    }
89
90    /// Squash-merge `branch` into the current branch.
91    ///
92    /// Equivalent to `git merge --squash <branch>`. The changes are staged
93    /// but **not** committed — callers should follow up with
94    /// [`commit_all`](Self::commit_all) or a raw commit to record them.
95    pub async fn squash_merge(&self, branch: impl Into<String>) -> Result<()> {
96        let mut cmd = MergeCommand::new();
97        cmd.squash().commit_ref(branch);
98        cmd.current_dir(self.repo.path());
99        cmd.execute().await?;
100        Ok(())
101    }
102}
103
104impl Repository {
105    /// High-level workflow compositions.
106    #[must_use]
107    pub fn workflow(&self) -> WorkflowOps<'_> {
108        WorkflowOps { repo: self }
109    }
110}