Skip to main content

git_vendor/
cli.rs

1use std::path::PathBuf;
2
3use clap::Parser;
4
5/// Merge strategy option for resolving conflicting regions during vendor
6/// merges.  These mirror the `-X` / `--strategy-option` values accepted by
7/// `git merge`.
8#[derive(Clone, Copy, Debug, Default, clap::ValueEnum)]
9pub enum StrategyOption {
10    /// Record conflicts in the index so that checkout produces conflict
11    /// markers in the working directory (the default).
12    #[default]
13    Normal,
14    /// Resolve conflicts by taking "ours" (the local side).
15    Ours,
16    /// Resolve conflicts by taking "theirs" (the upstream/vendor side).
17    Theirs,
18    /// Combine both sides, keeping each unique line (union merge).
19    Union,
20}
21
22impl StrategyOption {
23    /// Convert to the corresponding `git2::FileFavor`.
24    pub fn to_file_favor(self) -> git2::FileFavor {
25        match self {
26            StrategyOption::Normal => git2::FileFavor::Normal,
27            StrategyOption::Ours => git2::FileFavor::Ours,
28            StrategyOption::Theirs => git2::FileFavor::Theirs,
29            StrategyOption::Union => git2::FileFavor::Union,
30        }
31    }
32}
33
34#[derive(Parser)]
35#[command(name = "git vendor", bin_name = "git vendor")]
36#[command(
37    author,
38    version,
39    about = "An in-source vendoring alternative to Git submodules and subtrees.",
40    long_about = None
41)]
42pub struct Cli {
43    /// Path to the git repository. Defaults to the current directory.
44    #[arg(short = 'C', long, global = true)]
45    pub repo: Option<PathBuf>,
46
47    #[command(subcommand)]
48    pub command: Command,
49}
50
51#[derive(clap::Subcommand)]
52pub enum Command {
53    /// List all configured vendor sources.
54    List,
55
56    /// Add a new vendor source.
57    Add {
58        /// The remote URL to vendor from.
59        url: String,
60
61        /// A unique name for this vendor (used in config keys and ref names).
62        /// Defaults to the basename of the URL, minus any `.git` suffix.
63        #[arg(short, long)]
64        name: Option<String>,
65
66        /// The upstream branch to track (defaults to HEAD).
67        #[arg(short, long)]
68        branch: Option<String>,
69
70        /// Glob pattern(s) selecting which upstream files to vendor.
71        #[arg(short, long, default_value = "**")]
72        pattern: Vec<String>,
73
74        /// Local directory where vendored files are placed (defaults to current directory).
75        #[arg(long)]
76        path: Option<PathBuf>,
77
78        /// Strategy option for resolving conflicting regions during the merge.
79        #[arg(short = 'X', long = "strategy-option", value_enum, default_value_t)]
80        strategy_option: StrategyOption,
81    },
82
83    /// Fetch the latest upstream commits for one or all vendors.
84    Fetch {
85        /// Vendor name. If omitted, fetches all vendors.
86        name: Option<String>,
87    },
88
89    /// Remove a vendor source and its associated refs and attributes.
90    Rm {
91        /// Vendor name to remove.
92        name: String,
93    },
94
95    /// Add glob pattern(s) to an existing vendor's configuration.
96    Track {
97        /// Vendor name.
98        name: String,
99
100        /// Glob pattern(s) to add.
101        #[arg(short, long, required = true)]
102        pattern: Vec<String>,
103    },
104
105    /// Remove glob pattern(s) from an existing vendor's configuration.
106    Untrack {
107        /// Vendor name.
108        name: String,
109
110        /// Glob pattern(s) to remove.
111        #[arg(short, long, required = true)]
112        pattern: Vec<String>,
113    },
114
115    /// Show which vendors have unmerged upstream changes.
116    Status,
117
118    /// Clean up refs/vendor/* refs that have no corresponding entry in .gitvendors.
119    Prune,
120
121    /// Merge upstream changes for a vendor.
122    Merge {
123        /// Vendor name. Required unless `--all` is given or only one vendor
124        /// is configured.
125        name: Option<String>,
126
127        /// Merge all configured vendors.
128        #[arg(short, long)]
129        all: bool,
130
131        /// Strategy option for resolving conflicting regions during the merge.
132        #[arg(short = 'X', long = "strategy-option", value_enum, default_value_t)]
133        strategy_option: StrategyOption,
134    },
135
136    /// Fetch and merge upstream changes for a vendor.
137    Pull {
138        /// Vendor name. Required unless `--all` is given or only one vendor
139        /// is configured.
140        name: Option<String>,
141
142        /// Pull all configured vendors.
143        #[arg(short, long)]
144        all: bool,
145
146        /// Strategy option for resolving conflicting regions during the merge.
147        #[arg(short = 'X', long = "strategy-option", value_enum, default_value_t)]
148        strategy_option: StrategyOption,
149    },
150}
151
152/// Derive a vendor name from a URL by taking the last path component and
153/// stripping a trailing `.git` suffix, if present.
154///
155/// ```
156/// # use git_vendor::cli::name_from_url;
157/// assert_eq!(name_from_url("https://github.com/org/repo.git"), "repo");
158/// assert_eq!(name_from_url("https://github.com/org/repo"), "repo");
159/// assert_eq!(name_from_url("git@github.com:org/my-lib.git"), "my-lib");
160/// assert_eq!(name_from_url("/local/path/to/repo.git"), "repo");
161/// ```
162pub fn name_from_url(url: &str) -> &str {
163    let url = url.trim_end_matches('/');
164    let basename = url.rsplit_once('/').map_or(url, |(_, b)| b);
165    // Also handle SCP-style URLs like `git@host:path/repo.git`
166    let basename = basename.rsplit_once(':').map_or(basename, |(_, b)| b);
167    basename.strip_suffix(".git").unwrap_or(basename)
168}