gn_cli/commands/
doctor.rs1use anyhow::Result;
2use std::path::Path;
3use std::process::Command;
4
5pub fn run() -> Result<()> {
6 println!("\n\x1b[1;36m🩺 git-notes doctor\x1b[0m");
7 println!("Checking repository configuration and health...\n");
8
9 let mut errors = 0;
10 let mut warnings = 0;
11
12 let git_root = match Command::new("git").args(["rev-parse", "--show-toplevel"]).output() {
14 Ok(out) if out.status.success() => {
15 let root = String::from_utf8_lossy(&out.stdout).trim().to_string();
16 println!(" \x1b[32m✓\x1b[0m Git repository: {}", root);
17 Some(root)
18 }
19 _ => {
20 println!(" \x1b[31m✗\x1b[0m Git repository: not inside a valid git repo");
21 errors += 1;
22 None
23 }
24 };
25
26 let current_version = env!("CARGO_PKG_VERSION");
28 println!(" \x1b[32m✓\x1b[0m Binary version: v{}", current_version);
29
30 let namespaces = ["comments", "review", "todos"];
32 for ns in &namespaces {
33 let ref_path = format!("refs/notes/{}", ns);
34 let check_ref = Command::new("git")
35 .args(["for-each-ref", &ref_path, "--format=%(objectname)"])
36 .output();
37
38 match check_ref {
39 Ok(out) if out.status.success() && !out.stdout.is_empty() => {
40 let note_count = Command::new("git")
41 .args(["notes", "--ref", &ref_path, "list"])
42 .output()
43 .map(|o| String::from_utf8_lossy(&o.stdout).lines().count())
44 .unwrap_or(0);
45 println!(" \x1b[32m✓\x1b[0m {}: exists ({} note(s))", ref_path, note_count);
46 }
47 _ => {
48 println!(
49 " \x1b[33m⚠\x1b[0m {}: not initialized yet (run: git-notes sync pull)",
50 ref_path
51 );
52 warnings += 1;
53 }
54 }
55 }
56
57 let remote_config = Command::new("git")
59 .args(["config", "--get-all", "remote.origin.fetch"])
60 .output();
61
62 let has_notes_fetch = match remote_config {
63 Ok(out) if out.status.success() => {
64 String::from_utf8_lossy(&out.stdout).contains("refs/notes/*")
65 }
66 _ => false,
67 };
68
69 if has_notes_fetch {
70 println!(" \x1b[32m✓\x1b[0m Remote fetch refspec: configured for refs/notes/*");
71 } else {
72 println!(" \x1b[33m⚠\x1b[0m Remote fetch refspec: missing '+refs/notes/*:refs/notes/*'");
73 println!(" \x1b[90mFix with: git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*'\x1b[0m");
74 warnings += 1;
75 }
76
77 if let Some(ref root) = git_root {
79 let hooks_dir = Path::new(root).join(".git").join("hooks");
80 let post_merge = hooks_dir.join("post-merge");
81 let pre_push = hooks_dir.join("pre-push");
82
83 if post_merge.exists() {
84 println!(" \x1b[32m✓\x1b[0m post-merge hook: installed");
85 } else {
86 println!(" \x1b[33m⚠\x1b[0m post-merge hook: not installed (run: git-notes-hooks install)");
87 warnings += 1;
88 }
89
90 if pre_push.exists() {
91 println!(" \x1b[32m✓\x1b[0m pre-push hook: installed");
92 } else {
93 println!(" \x1b[33m⚠\x1b[0m pre-push hook: not installed");
94 warnings += 1;
95 }
96
97 let mut stale_count = 0;
99 let notes_out = Command::new("git")
100 .args(["notes", "--ref", "refs/notes/comments", "list"])
101 .output();
102
103 if let Ok(out) = notes_out {
104 for line in String::from_utf8_lossy(&out.stdout).lines() {
105 if let Some(blob) = line.split_whitespace().next() {
106 if let Ok(cat) = Command::new("git").args(["cat-file", "blob", blob]).output() {
107 if let Ok(note) = serde_json::from_slice::<serde_json::Value>(&cat.stdout) {
108 if let Some(rel_file) = note.get("file").and_then(|f| f.as_str()) {
109 if !rel_file.is_empty() && !Path::new(root).join(rel_file).exists() {
110 stale_count += 1;
111 println!(
112 " \x1b[33m⚠\x1b[0m Stale note {}: referenced file '{}' no longer exists",
113 &blob[..8.min(blob.len())],
114 rel_file
115 );
116 }
117 }
118 }
119 }
120 }
121 }
122 }
123
124 if stale_count == 0 {
125 println!(" \x1b[32m✓\x1b[0m Stale notes: all referenced files exist in working tree");
126 } else {
127 warnings += stale_count;
128 }
129 }
130
131 println!();
133 if errors == 0 && warnings == 0 {
134 println!("\x1b[32m✨ All checks passed! git-notes is in peak health.\x1b[0m\n");
135 } else {
136 println!(
137 "\x1b[1mSummary: \x1b[31m{} error(s)\x1b[0m, \x1b[33m{} warning(s)\x1b[0m\n",
138 errors, warnings
139 );
140 }
141
142 println!("┌─────────────────────────────────────────────────────────────┐");
143 println!("│ \x1b[1;36m💬 Enjoying git-notes? Help us grow!\x1b[0m │");
144 println!("│ \x1b[33m⭐ Star on GitHub:\x1b[0m https://github.com/isaim0011/git-notes │");
145 println!("│ \x1b[35m★ Review on Open VSX:\x1b[0m https://open-vsx.org/extension/isaim0011/vscode-git-notes │");
146 println!("└─────────────────────────────────────────────────────────────┘\n");
147
148 Ok(())
149}