1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use clap::{ArgGroup, Parser};
use std::path::PathBuf;
/// It reads a VCS repos file and add listed repositories as Git
/// submodules.
#[derive(Debug, Clone, Parser)]
#[clap(group(
ArgGroup::new("selection")
.args(&["only"])
.conflicts_with_all(&["ignore"])
))]
pub struct Opts {
/// The YAML file of a repository list.
pub repo_file: PathBuf,
/// The directory to add submodules.
pub prefix: PathBuf,
/// Process only these repositories (mutually exclusive with --ignore).
#[clap(long, value_name = "REPO")]
pub only: Option<Vec<PathBuf>>,
/// Process all repositories except these (mutually exclusive with --only).
#[clap(long, value_name = "REPO")]
pub ignore: Option<Vec<PathBuf>>,
/// Do not checkout the files in each submodule.
#[clap(long)]
pub no_checkout: bool,
/// Skip updating existing submodules (by default, existing submodules are updated).
#[clap(long)]
pub skip_existing: bool,
/// Remove submodules that are not in the current selection.
#[clap(long)]
pub sync_selection: bool,
/// Preview what would be done without making changes.
#[clap(long)]
pub dry_run: bool,
}
impl Opts {
/// Check if we should update existing submodules
pub fn should_update(&self) -> bool {
!self.skip_existing
}
/// Get the selected repositories (handles both --only and deprecated --select)
pub fn get_selected(&self) -> &Option<Vec<PathBuf>> {
&self.only
}
/// Get the ignored repositories (handles both --ignore and deprecated --skip)
pub fn get_ignored(&self) -> &Option<Vec<PathBuf>> {
&self.ignore
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opts_parsing() {
// Test basic argument parsing
let opts = Opts::try_parse_from(["vcs2git", "test.repos", "src"]).unwrap();
assert_eq!(opts.repo_file, PathBuf::from("test.repos"));
assert_eq!(opts.prefix, PathBuf::from("src"));
assert!(!opts.no_checkout);
assert!(!opts.skip_existing);
assert!(opts.only.is_none());
assert!(opts.ignore.is_none());
assert!(!opts.sync_selection);
assert!(!opts.dry_run);
}
#[test]
fn test_new_flags() {
let opts = Opts::try_parse_from([
"vcs2git",
"--no-checkout",
"--skip-existing",
"--only",
"repo1",
"--only",
"repo2",
"--sync-selection",
"test.repos",
"src",
])
.unwrap();
assert!(opts.no_checkout);
assert!(opts.skip_existing);
assert!(opts.sync_selection);
assert_eq!(opts.only.as_ref().unwrap().len(), 2);
assert!(opts.ignore.is_none());
}
#[test]
fn test_mutually_exclusive_flags() {
// --only and --ignore are mutually exclusive
let result = Opts::try_parse_from([
"vcs2git",
"--only",
"repo1",
"--ignore",
"repo2",
"test.repos",
"src",
]);
assert!(result.is_err());
}
}