git_prole/
cli.rs

1use camino::Utf8PathBuf;
2use clap::Args;
3use clap::Parser;
4use clap::Subcommand;
5
6/// A `git-worktree(1)` manager.
7#[derive(Debug, Clone, Parser)]
8#[command(version, author, about)]
9#[command(max_term_width = 100, disable_help_subcommand = true)]
10pub struct Cli {
11    /// Log filter directives, of the form `target[span{field=value}]=level`, where all components
12    /// except the level are optional.
13    ///
14    /// Try `debug` or `trace`.
15    #[arg(long, default_value = "info", env = "GIT_PROLE_LOG", global = true)]
16    pub log: String,
17
18    /// If set, do not perform any actions, and instead only construct and print a plan.
19    #[arg(long, visible_alias = "dry", default_value = "false", global = true)]
20    pub dry_run: bool,
21
22    /// The location to read the configuration file from. Defaults to
23    /// `~/.config/git-prole/config.toml`.
24    #[arg(long, global = true)]
25    pub config: Option<Utf8PathBuf>,
26
27    #[command(subcommand)]
28    pub command: Command,
29}
30
31impl Cli {
32    /// A fake stub CLI for testing.
33    #[cfg(test)]
34    pub fn test_stub() -> Self {
35        Self {
36            log: "info".to_owned(),
37            dry_run: false,
38            config: None,
39            command: Command::Convert(ConvertArgs {
40                default_branch: None,
41                destination: None,
42            }),
43        }
44    }
45}
46
47#[allow(rustdoc::bare_urls)]
48#[derive(Debug, Clone, Subcommand)]
49pub enum Command {
50    /// Convert a repository into a worktree checkout.
51    ///
52    /// This will convert the repository in the current directory into a worktree repository. This includes:
53    ///
54    /// - Making the repository a bare repository.
55    ///
56    /// - Converting the current checkout (branch, commit, whatever) into a worktree.
57    ///   Uncommited changes will be kept, but will not remain unstaged.
58    ///
59    /// - Creating a new worktree for the default branch.
60    Convert(ConvertArgs),
61
62    /// Clone a repository into a worktree checkout.
63    ///
64    /// If you have `gh` installed and the URL looks `gh`-like and isn't an existing local path,
65    /// I'll pass the repository URL to that.
66    ///
67    /// This is just a regular `git clone` followed by `git prole convert`.
68    Clone(CloneArgs),
69
70    /// Add a new worktree.
71    ///
72    /// This command tries to guess what you want, and as a result the behavior can be a little bit
73    /// subtle! If given, the `--branch` argument will always create a new branch, and the
74    /// `COMMITISH` argument will always be checked out in the new worktree; use those to
75    /// disambiguate when necessary.
76    ///
77    /// Unlike `git worktree add`, this will set new worktrees to start at and track the default
78    /// branch by default, rather than the checked out commit or branch of the worktree the command
79    /// is run from.
80    ///
81    /// By default, untracked files are copied to the new worktree.
82    Add(AddArgs),
83
84    /// Initialize the configuration file.
85    #[command(subcommand)]
86    Config(ConfigCommand),
87
88    /// Generate shell completions.
89    Completions {
90        /// Shell to generate completions for.
91        shell: clap_complete::shells::Shell,
92    },
93
94    /// Generate man pages.
95    #[cfg(feature = "clap_mangen")]
96    Manpages {
97        /// Directory to write man pages to.
98        out_dir: camino::Utf8PathBuf,
99    },
100}
101
102#[derive(Args, Clone, Debug)]
103pub struct ConvertArgs {
104    /// A default branch to create a worktree for.
105    #[arg(long)]
106    pub default_branch: Option<String>,
107
108    /// The directory to place the worktrees into.
109    #[arg()]
110    pub destination: Option<Utf8PathBuf>,
111}
112
113#[derive(Args, Clone, Debug)]
114pub struct CloneArgs {
115    /// The repository URL to clone.
116    #[arg()]
117    pub repository: String,
118
119    /// The directory to setup the worktrees in.
120    ///
121    /// Defaults to the last component of the repository URL, with a trailing `.git` removed.
122    #[arg()]
123    pub directory: Option<Utf8PathBuf>,
124
125    /// Extra arguments to forward to `git clone`.
126    #[arg(last = true)]
127    pub clone_args: Vec<String>,
128}
129
130#[derive(Args, Clone, Debug)]
131pub struct AddArgs {
132    #[command(flatten)]
133    pub inner: AddArgsInner,
134
135    /// The commit to check out in the new worktree.
136    ///
137    /// If this is the name of a unique remote branch, then a local branch with the same name will
138    /// be created to track the remote branch.
139    #[arg()]
140    pub commitish: Option<String>,
141
142    /// Extra arguments to forward to `git worktree add`.
143    #[arg(last = true)]
144    pub worktree_add_args: Vec<String>,
145}
146
147#[derive(Args, Clone, Debug)]
148#[group(required = true, multiple = true)]
149pub struct AddArgsInner {
150    /// Create a new branch with the given name instead of checking out an existing branch.
151    ///
152    /// This will refuse to reset a branch if it already exists; use `--force-branch`/`-B` to
153    /// reset existing branches.
154    #[arg(
155        long,
156        short = 'b',
157        visible_alias = "create",
158        visible_short_alias = 'c',
159        conflicts_with_all = ["force_branch", "detach"],
160    )]
161    pub branch: Option<String>,
162
163    /// Create a new branch with the given name, overwriting any existing branch with the same
164    /// name.
165    #[arg(
166        long,
167        short = 'B',
168        visible_alias = "force-create",
169        visible_short_alias = 'C',
170        conflicts_with_all = ["branch", "detach"],
171    )]
172    pub force_branch: Option<String>,
173
174    /// Create the new worktree in detached mode, not checked out on any branch.
175    #[arg(
176        long,
177        short = 'd',
178        alias = "detached",
179        conflicts_with_all = ["branch", "force_branch"],
180    )]
181    pub detach: bool,
182
183    /// The new worktree's name or path.
184    ///
185    /// If the name contains a `/`, it's assumed to be a path. Otherwise, it's assumed to be a
186    /// worktree name: it's used as a name in the same directory as the other worktrees, and (by
187    /// default) a branch with that name is checked out or created. (When this is a path, only the
188    /// last component of the path is used as the branch name.)
189    #[arg()]
190    pub name_or_path: Option<String>,
191}
192
193#[derive(Debug, Clone, Subcommand)]
194pub enum ConfigCommand {
195    /// Initialize a default configuration file.
196    Init(ConfigInitArgs),
197}
198
199#[derive(Args, Clone, Debug)]
200pub struct ConfigInitArgs {
201    /// The location to write the configuration file. Can be `-` for stdout. Defaults to
202    /// `~/.config/git-prole/config.toml`.
203    pub output: Option<Utf8PathBuf>,
204}