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}