git_proc/checkout.rs
1//! `git checkout` command builder using a typestate to enforce that a target
2//! is chosen before the command can be executed.
3//!
4//! # Example
5//!
6//! ```
7//! use git_proc::{Build, branch::Branch, checkout};
8//! let main = Branch::from_static_or_panic("main");
9//! let _cmd = checkout::new().commit_ish(&main).build();
10//! ```
11//!
12//! # Typestate guarantees
13//!
14//! Executing without a target is a compile error:
15//!
16//! ```compile_fail
17//! use git_proc::{Build, checkout};
18//! let _cmd = checkout::new().build();
19//! ```
20//!
21//! Selecting more than one target is a compile error:
22//!
23//! ```compile_fail
24//! use git_proc::{branch::Branch, checkout, tag::Tag};
25//! let b = Branch::from_static_or_panic("main");
26//! let t = Tag::from_static_or_panic("v1.0.0");
27//! let _ = checkout::new().commit_ish(&b).commit_ish(&t);
28//! ```
29
30use std::marker::PhantomData;
31use std::path::Path;
32
33use crate::CommandError;
34use crate::commit_ish::{CommitIsh, NoTarget, WithTarget};
35
36/// Create a new `git checkout` command builder.
37#[must_use]
38pub fn new() -> Checkout<'static, NoTarget> {
39 Checkout {
40 repo_path: None,
41 state: NoTarget,
42 _phantom: PhantomData,
43 }
44}
45
46/// Builder for `git checkout`.
47///
48/// `S` encodes whether a target has been chosen. Only
49/// `Checkout<_, WithTarget>` exposes `status()` / `Build`, so failing to
50/// specify a target is a compile error.
51///
52/// See `git checkout --help` for full documentation.
53#[derive(Debug)]
54pub struct Checkout<'a, S> {
55 repo_path: Option<&'a Path>,
56 state: S,
57 _phantom: PhantomData<&'a ()>,
58}
59
60impl<'a, S> Checkout<'a, S> {
61 /// Set the repository path (`-C <path>`).
62 #[must_use]
63 pub fn repo_path(mut self, path: &'a Path) -> Self {
64 self.repo_path = Some(path);
65 self
66 }
67}
68
69impl<'a, S> crate::RepoPath<'a> for Checkout<'a, S> {
70 fn repo_path(self, path: &'a Path) -> Self {
71 self.repo_path(path)
72 }
73}
74
75impl<'a> Checkout<'a, NoTarget> {
76 /// Select the commit-ish to check out.
77 #[must_use]
78 pub fn commit_ish(self, commit_ish: impl Into<CommitIsh<'a>>) -> Checkout<'a, WithTarget<'a>> {
79 Checkout {
80 repo_path: self.repo_path,
81 state: WithTarget {
82 target: commit_ish.into(),
83 },
84 _phantom: PhantomData,
85 }
86 }
87}
88
89impl<'a> Checkout<'a, WithTarget<'a>> {
90 /// Execute the command and return the exit status.
91 pub async fn status(self) -> Result<(), CommandError> {
92 crate::Build::build(self).status().await
93 }
94}
95
96impl<'a> crate::Build for Checkout<'a, WithTarget<'a>> {
97 fn build(self) -> cmd_proc::Command {
98 crate::base_command(self.repo_path)
99 .argument("checkout")
100 .argument(self.state.target)
101 }
102}
103
104#[cfg(feature = "test-utils")]
105impl<'a> Checkout<'a, WithTarget<'a>> {
106 /// Compare the built command with another command using debug representation.
107 pub fn test_eq(&self, other: &cmd_proc::Command) {
108 let command = crate::Build::build(Self {
109 repo_path: self.repo_path,
110 state: WithTarget {
111 target: self.state.target,
112 },
113 _phantom: PhantomData,
114 });
115 command.test_eq(other);
116 }
117}