Skip to main content

## Module git

Source
Available on crate feature `unstable-doc` only.
Expand description

## §Example: git-like CLI (Builder API)

    use std::ffi::OsString;
    use std::path::PathBuf;

    use clap::{Command, arg};

    fn cli() -> Command {
        Command::new("git")
            .about("A fictional versioning CLI")
            .subcommand_required(true)
            .arg_required_else_help(true)
            .allow_external_subcommands(true)
            .subcommand(
                Command::new("clone")
                    .about("Clones repos")
                    .arg(arg!(<REMOTE> "The remote to clone"))
                    .arg_required_else_help(true),
            )
            .subcommand(
                Command::new("diff")
                    .about("Compare two commits")
                    .arg(arg!(base: [COMMIT]))
                    .arg(arg!(head: [COMMIT]))
                    .arg(arg!(path: [PATH]).last(true))
                    .arg(
                        arg!(--color <WHEN>)
                            .value_parser(["always", "auto", "never"])
                            .num_args(0..=1)
                            .require_equals(true)
                            .default_value("auto")
                            .default_missing_value("always"),
                    ),
            )
            .subcommand(
                Command::new("push")
                    .about("pushes things")
                    .arg(arg!(<REMOTE> "The remote to target"))
                    .arg_required_else_help(true),
            )
            .subcommand(
                Command::new("add")
                    .about("adds things")
                    .arg_required_else_help(true)
                    .arg(arg!(<PATH> ... "Stuff to add").value_parser(clap::value_parser!(PathBuf))),
            )
            .subcommand(
                Command::new("stash")
                    .args_conflicts_with_subcommands(true)
                    .flatten_help(true)
                    .args(push_args())
                    .subcommand(Command::new("push").args(push_args()))
                    .subcommand(Command::new("pop").arg(arg!([STASH])))
                    .subcommand(Command::new("apply").arg(arg!([STASH]))),
            )
    }

    fn push_args() -> Vec<clap::Arg> {
        vec![arg!(-m --message <MESSAGE>)]
    }

    fn main() {
        let matches = cli().get_matches();

        match matches.subcommand() {
            Some(("clone", sub_matches)) => {
                println!(
                    "Cloning {}",
                    sub_matches.get_one::<String>("REMOTE").expect("required")
                );
            }
            Some(("diff", sub_matches)) => {
                let color = sub_matches
                    .get_one::<String>("color")
                    .map(|s| s.as_str())
                    .expect("defaulted in clap");

                let mut base = sub_matches.get_one::<String>("base").map(|s| s.as_str());
                let mut head = sub_matches.get_one::<String>("head").map(|s| s.as_str());
                let mut path = sub_matches.get_one::<String>("path").map(|s| s.as_str());
                if path.is_none() {
                    path = head;
                    head = None;
                    if path.is_none() {
                        path = base;
                        base = None;
                    }
                }
                let base = base.unwrap_or("stage");
                let head = head.unwrap_or("worktree");
                let path = path.unwrap_or("");
                println!("Diffing {base}..{head} {path} (color={color})");
            }
            Some(("push", sub_matches)) => {
                println!(
                    "Pushing to {}",
                    sub_matches.get_one::<String>("REMOTE").expect("required")
                );
            }
            Some(("add", sub_matches)) => {
                let paths = sub_matches
                    .get_many::<PathBuf>("PATH")
                    .into_iter()
                    .flatten()
                    .collect::<Vec<_>>();
                println!("Adding {paths:?}");
            }
            Some(("stash", sub_matches)) => {
                let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches));
                match stash_command {
                    ("apply", sub_matches) => {
                        let stash = sub_matches.get_one::<String>("STASH");
                        println!("Applying {stash:?}");
                    }
                    ("pop", sub_matches) => {
                        let stash = sub_matches.get_one::<String>("STASH");
                        println!("Popping {stash:?}");
                    }
                    ("push", sub_matches) => {
                        let message = sub_matches.get_one::<String>("message");
                        println!("Pushing {message:?}");
                    }
                    (name, _) => {
                        unreachable!("Unsupported subcommand `{name}`")
                    }
                }
            }
            Some((ext, sub_matches)) => {
                let args = sub_matches
                    .get_many::<OsString>("")
                    .into_iter()
                    .flatten()
                    .collect::<Vec<_>>();
                println!("Calling out to {ext:?} with {args:?}");
            }
            _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!()
        }

        // Continued program logic goes here...
    }

Git is an example of several common subcommand patterns.

Help:

    $ git
    ? failed
    A fictional versioning CLI

    Usage: git[EXE] <COMMAND>

    Commands:
      clone  Clones repos
      diff   Compare two commits
      push   pushes things
      add    adds things
      stash
      help   Print this message or the help of the given subcommand(s)

    Options:
      -h, --help  Print help

    $ git help
    A fictional versioning CLI

    Usage: git[EXE] <COMMAND>

    Commands:
      clone  Clones repos
      diff   Compare two commits
      push   pushes things
      add    adds things
      stash
      help   Print this message or the help of the given subcommand(s)

    Options:
      -h, --help  Print help

    $ git help add
    adds things

    Usage: git[EXE] add <PATH>...

    Arguments:
      <PATH>...  Stuff to add

    Options:
      -h, --help  Print help

A basic argument:

    $ git add
    ? failed
    adds things

    Usage: git[EXE] add <PATH>...

    Arguments:
      <PATH>...  Stuff to add

    Options:
      -h, --help  Print help

    $ git add Cargo.toml Cargo.lock
    Adding ["Cargo.toml", "Cargo.lock"]

Default subcommand:

    $ git stash -h
    Usage: git[EXE] stash [OPTIONS]
           git[EXE] stash push [OPTIONS]
           git[EXE] stash pop [STASH]
           git[EXE] stash apply [STASH]
           git[EXE] stash help [COMMAND]...

    Options:
      -m, --message <MESSAGE>
      -h, --help               Print help

    git[EXE] stash push:
      -m, --message <MESSAGE>
      -h, --help               Print help

    git[EXE] stash pop:
      -h, --help  Print help
      [STASH]

    git[EXE] stash apply:
      -h, --help  Print help
      [STASH]

    git[EXE] stash help:
    Print this message or the help of the given subcommand(s)
      [COMMAND]...  Print help for the subcommand(s)

    $ git stash push -h
    Usage: git[EXE] stash push [OPTIONS]

    Options:
      -m, --message <MESSAGE>
      -h, --help               Print help

    $ git stash pop -h
    Usage: git[EXE] stash pop [STASH]

    Arguments:
      [STASH]

    Options:
      -h, --help  Print help

    $ git stash -m "Prototype"
    Pushing Some("Prototype")

    $ git stash pop
    Popping None

    $ git stash push -m "Prototype"
    Pushing Some("Prototype")

    $ git stash pop
    Popping None

External subcommands:

    $ git custom-tool arg1 --foo bar
    Calling out to "custom-tool" with ["arg1", "--foo", "bar"]

Last argument:

    $ git diff --help
    Compare two commits

    Usage: git[EXE] diff [OPTIONS] [COMMIT] [COMMIT] [-- <PATH>]

    Arguments:
      [COMMIT]
      [COMMIT]
      [PATH]

    Options:
          --color[=<WHEN>]  [default: auto] [possible values: always, auto, never]
      -h, --help            Print help

    $ git diff
    Diffing stage..worktree  (color=auto)

    $ git diff ./src
    Diffing stage..worktree ./src (color=auto)

    $ git diff HEAD ./src
    Diffing HEAD..worktree ./src (color=auto)

    $ git diff HEAD~~ -- HEAD
    Diffing HEAD~~..worktree HEAD (color=auto)

    $ git diff --color
    Diffing stage..worktree  (color=always)

    $ git diff --color=never
    Diffing stage..worktree  (color=never)