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}