Skip to content

Commit 17a4877

Browse files
committed
Fix: gix submodule resolution bug, verbose output (added --verbose flag)
1 parent 45165a8 commit 17a4877

12 files changed

Lines changed: 234 additions & 132 deletions

src/commands.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ pub struct Cli {
7373
/// Path to the configuration file (default: submod.toml).
7474
#[arg(long = "config", global = true, default_value = "submod.toml", value_parser = clap::value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath, help = "Optionally provide a different configuration file path. Defaults to submod.toml in the current directory.")]
7575
pub config: PathBuf,
76+
77+
/// Enable verbose output with detailed status information.
78+
#[arg(long, short, global = true)]
79+
pub verbose: bool,
7680
}
7781

7882
/// Supported commands for the `submod` tool.

src/git_manager.rs

Lines changed: 92 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ pub struct GitManager {
147147
config: Config,
148148
/// Path to the configuration file
149149
config_path: PathBuf,
150+
/// Whether to print verbose output
151+
verbose: bool,
150152
}
151153

152154
impl GitManager {
@@ -296,7 +298,8 @@ impl GitManager {
296298
Ok(())
297299
}
298300

299-
/// Creates a new `GitManager` by loading configuration from the given path.
301+
/// Creates a new `GitManager` by loading configuration from the given path
302+
/// with default (non-verbose) output.
300303
///
301304
/// # Arguments
302305
///
@@ -306,9 +309,15 @@ impl GitManager {
306309
///
307310
/// Returns `SubmoduleError::RepositoryError` if the repository cannot be discovered,
308311
/// or `SubmoduleError::ConfigError` if the configuration fails to load.
312+
#[allow(dead_code)]
309313
pub fn new(config_path: PathBuf) -> Result<Self, SubmoduleError> {
314+
Self::with_verbose(config_path, false)
315+
}
316+
317+
/// Creates a new `GitManager` with the specified verbosity level.
318+
pub fn with_verbose(config_path: PathBuf, verbose: bool) -> Result<Self, SubmoduleError> {
310319
// Use GitOpsManager for repository detection and operations
311-
let git_ops = GitOpsManager::new(Some(Path::new(".")))
320+
let git_ops = GitOpsManager::new(Some(Path::new(".")), verbose)
312321
.map_err(|_| SubmoduleError::RepositoryError)?;
313322

314323
let config = Config::default()
@@ -319,6 +328,7 @@ impl GitManager {
319328
git_ops,
320329
config,
321330
config_path,
331+
verbose,
322332
})
323333
}
324334

@@ -628,7 +638,9 @@ impl GitManager {
628638
SubmoduleError::GitoxideError(format!("GitOpsManager update failed: {e}"))
629639
})?;
630640

631-
println!("✅ Updated {name} successfully");
641+
if self.verbose {
642+
println!("✅ Updated {name} successfully");
643+
}
632644
Ok(())
633645
}
634646

@@ -694,7 +706,9 @@ impl GitManager {
694706
let submodule_path = Path::new(path_str);
695707

696708
if submodule_path.exists() && submodule_path.join(".git").exists() {
697-
println!("✅ {name} already initialized");
709+
if self.verbose {
710+
println!("✅ {name} already initialized");
711+
}
698712
// Even if already initialized, check if we need to configure sparse checkout
699713
let sparse_paths_opt = self
700714
.config
@@ -707,7 +721,9 @@ impl GitManager {
707721
return Ok(());
708722
}
709723

710-
println!("🔄 Initializing {name}...");
724+
if self.verbose {
725+
println!("🔄 Initializing {name}...");
726+
}
711727

712728
let workdir = std::path::Path::new(".");
713729

@@ -748,7 +764,9 @@ impl GitManager {
748764
.map_err(Self::map_git_ops_error)?;
749765
}
750766

751-
println!(" ✅ Initialized using git submodule commands: {path_str}");
767+
if self.verbose {
768+
println!(" ✅ Initialized using git submodule commands: {path_str}");
769+
}
752770

753771
// Configure sparse checkout if specified
754772
if let Some(sparse_checkouts) = submodules.sparse_checkouts() {
@@ -757,85 +775,110 @@ impl GitManager {
757775
}
758776
}
759777

760-
println!("✅ {name} initialized");
778+
if self.verbose {
779+
println!("✅ {name} initialized");
780+
}
761781
Ok(())
762782
}
763783

764784
/// Check all submodules using gitoxide APIs where possible
765785
pub fn check_all_submodules(&self) -> Result<(), SubmoduleError> {
766-
println!("Checking submodule configurations...");
786+
if self.verbose {
787+
println!("Checking submodule configurations...");
788+
}
767789

768790
for (submodule_name, submodule) in self.config.get_submodules() {
769-
println!("\n📁 {submodule_name}");
770-
771791
// Handle missing path gracefully - report but don't fail
772792
let path_str = if let Some(path) = submodule.path.as_ref() {
773793
path
774794
} else {
775-
println!(" ❌ Configuration error: No path configured");
795+
// Always show errors regardless of verbosity
796+
println!(" ❌ {submodule_name}: No path configured");
776797
continue;
777798
};
778799

779800
// Handle missing URL gracefully - report but don't fail
780801
if submodule.url.is_none() {
781-
println!(" ❌ Configuration error: No URL configured");
802+
println!(" ❌ {submodule_name}: No URL configured");
782803
continue;
783804
}
784805

785806
let submodule_path = Path::new(path_str);
786807
let git_path = submodule_path.join(".git");
787808

788809
if !submodule_path.exists() {
789-
println!(" ❌ Folder missing: {path_str}");
810+
println!(" ❌ {submodule_name}: Folder missing ({path_str})");
790811
continue;
791812
}
792813

793814
if !git_path.exists() {
794-
println!(" ❌ Not a git repository");
815+
println!(" ❌ {submodule_name}: Not a git repository");
795816
continue;
796817
}
797818

798819
// GITOXIDE API: Use gix::open and status check
799820
match self.check_submodule_repository_status(path_str, submodule_name) {
800821
Ok(status) => {
801-
println!(" ✅ Git repository exists");
822+
if self.verbose {
823+
println!("\n📁 {submodule_name}");
824+
println!(" ✅ Git repository exists");
825+
826+
if status.is_clean {
827+
println!(" ✅ Working tree is clean");
828+
} else {
829+
println!(" ⚠️ Working tree has changes");
830+
}
802831

803-
if status.is_clean {
804-
println!(" ��� Working tree is clean");
805-
} else {
806-
println!(" ⚠️ Working tree has changes");
807-
}
832+
if let Some(commit) = &status.current_commit {
833+
println!(" ✅ Current commit: {}", &commit[..8]);
834+
}
808835

809-
if let Some(commit) = &status.current_commit {
810-
println!(" ✅ Current commit: {}", &commit[..8]);
811-
}
836+
if status.has_remotes {
837+
println!(" ✅ Has remotes configured");
838+
} else {
839+
println!(" ⚠️ No remotes configured");
840+
}
812841

813-
if status.has_remotes {
814-
println!(" ✅ Has remotes configured");
815-
} else {
816-
println!(" ⚠️ No remotes configured");
817-
}
842+
match &status.sparse_status {
843+
SparseStatus::NotEnabled => {}
844+
SparseStatus::NotConfigured => {
845+
println!(" ❌ Sparse checkout not configured");
846+
}
847+
SparseStatus::Correct => {
848+
println!(" ✅ Sparse checkout configured correctly");
849+
}
850+
SparseStatus::Mismatch { expected, actual } => {
851+
println!(" ❌ Sparse checkout mismatch");
852+
println!(" Expected: {expected:?}");
853+
println!(" Current: {actual:?}");
854+
}
855+
}
818856

819-
match status.sparse_status {
820-
SparseStatus::NotEnabled => {}
821-
SparseStatus::NotConfigured => {
822-
println!(" ❌ Sparse checkout not configured");
857+
// Show effective settings
858+
self.show_effective_settings(submodule_name, submodule);
859+
} else {
860+
// Non-verbose: only print warnings/problems
861+
if !status.is_clean {
862+
println!(" ⚠️ {submodule_name}: Working tree has changes");
823863
}
824-
SparseStatus::Correct => {
825-
println!(" ✅ Sparse checkout configured correctly");
864+
if !status.has_remotes {
865+
println!(" ⚠️ {submodule_name}: No remotes configured");
826866
}
827-
SparseStatus::Mismatch { expected, actual } => {
828-
println!(" ❌ Sparse checkout mismatch");
829-
println!(" Expected: {expected:?}");
830-
println!(" Current: {actual:?}");
867+
match &status.sparse_status {
868+
SparseStatus::NotEnabled | SparseStatus::Correct => {}
869+
SparseStatus::NotConfigured => {
870+
println!(" ❌ {submodule_name}: Sparse checkout not configured");
871+
}
872+
SparseStatus::Mismatch { expected, actual } => {
873+
println!(" ❌ {submodule_name}: Sparse checkout mismatch");
874+
println!(" Expected: {expected:?}");
875+
println!(" Current: {actual:?}");
876+
}
831877
}
832878
}
833-
834-
// Show effective settings
835-
self.show_effective_settings(submodule_name, submodule);
836879
}
837880
Err(e) => {
838-
println!(" ❌ Cannot analyze repository: {e}");
881+
println!(" ❌ {submodule_name}: Cannot analyze repository: {e}");
839882
}
840883
}
841884
}
@@ -847,13 +890,13 @@ impl GitManager {
847890
println!(" 📋 Effective settings:");
848891

849892
if let Some(ignore) = &config.ignore {
850-
println!(" ignore = {:?}", ignore);
893+
println!(" ignore = {ignore:?}");
851894
}
852895
if let Some(update) = &config.update {
853-
println!(" update = {:?}", update);
896+
println!(" update = {update:?}");
854897
}
855898
if let Some(branch) = &config.branch {
856-
println!(" branch = {:?}", branch);
899+
println!(" branch = {branch:?}");
857900
}
858901
}
859902
/// Get reference to the underlying config
@@ -1648,8 +1691,9 @@ impl GitManager {
16481691

16491692
if from_setup {
16501693
// Read .gitmodules from the repo and convert to our config format
1651-
let git_ops = crate::git_ops::GitOpsManager::new(Some(std::path::Path::new(".")))
1652-
.map_err(|_| SubmoduleError::RepositoryError)?;
1694+
let git_ops =
1695+
crate::git_ops::GitOpsManager::new(Some(std::path::Path::new(".")), false)
1696+
.map_err(|_| SubmoduleError::RepositoryError)?;
16531697
let entries = git_ops.read_gitmodules().map_err(|e| {
16541698
SubmoduleError::ConfigError(format!("Failed to read .gitmodules: {e}"))
16551699
})?;
@@ -1662,6 +1706,7 @@ impl GitManager {
16621706
git_ops,
16631707
config,
16641708
config_path: output.to_path_buf(),
1709+
verbose: false,
16651710
};
16661711
tmp_manager.write_full_config()?;
16671712
println!(

src/git_ops/gix_ops.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ impl GixOperations {
6868
for section in as_config_file.sections() {
6969
// we need to convert everything to String and add to map
7070
let mut section_entries = std::collections::HashMap::new();
71-
let name = if section.header().subsection_name().is_some() {
72-
section.header().name().to_string()
71+
let name = if let Some(subsection) = section.header().subsection_name() {
72+
subsection.to_string()
7373
} else {
7474
section.header().name().to_string()
7575
};

src/git_ops/mod.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,22 @@ pub trait GitOperations {
170170
pub struct GitOpsManager {
171171
gix_ops: Option<GixOperations>,
172172
git2_ops: Git2Operations,
173+
verbose: bool,
173174
}
174175

175176
/// Implement GitOperations for GitOpsManager, using gix first and falling back to git2 if gix fails
176177
impl GitOpsManager {
177-
/// Create a new GitOpsManager with automatic fallback
178-
pub fn new(repo_path: Option<&Path>) -> Result<Self> {
178+
/// Create a new `GitOpsManager` with automatic fallback
179+
pub fn new(repo_path: Option<&Path>, verbose: bool) -> Result<Self> {
179180
let gix_ops = GixOperations::new(repo_path).ok();
180181
let git2_ops = Git2Operations::new(repo_path)
181182
.with_context(|| "Failed to initialize git2 operations")?;
182183

183-
Ok(Self { gix_ops, git2_ops })
184+
Ok(Self {
185+
gix_ops,
186+
git2_ops,
187+
verbose,
188+
})
184189
}
185190

186191
/// Return the working directory of the underlying git repository, if any.
@@ -212,11 +217,13 @@ impl GitOpsManager {
212217
self.gix_ops = Some(new_gix);
213218
}
214219
Err(e) => {
215-
eprintln!(
216-
"Warning: failed to reopen gix repository at {}: {}",
217-
workdir.display(),
218-
e
219-
);
220+
if self.verbose {
221+
eprintln!(
222+
"Warning: failed to reopen gix repository at {}: {}",
223+
workdir.display(),
224+
e
225+
);
226+
}
220227
}
221228
}
222229

@@ -233,7 +240,9 @@ impl GitOpsManager {
233240
match gix_op(gix) {
234241
Ok(result) => return Ok(result),
235242
Err(e) => {
236-
eprintln!("gix operation failed, falling back to git2: {}", e);
243+
if self.verbose {
244+
eprintln!("gix operation failed, falling back to git2: {e}");
245+
}
237246
}
238247
}
239248
}
@@ -251,7 +260,9 @@ impl GitOpsManager {
251260
match gix_op(gix) {
252261
Ok(result) => return Ok(result),
253262
Err(e) => {
254-
eprintln!("gix operation failed, falling back to git2: {}", e);
263+
if self.verbose {
264+
eprintln!("gix operation failed, falling back to git2: {e}");
265+
}
255266
}
256267
}
257268
}

0 commit comments

Comments
 (0)