Skip to main content

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}