qtcloud_devops_cli/code/
status.rs1use std::path::PathBuf;
2
3use super::model::{ComponentStatus, StatusReport, SyncStatus};
4use crate::git::{RepoState, SubmoduleStatus};
5
6fn map_status(s: &SubmoduleStatus) -> SyncStatus {
7 match s {
8 SubmoduleStatus::Clean => SyncStatus::Synced,
9 SubmoduleStatus::AheadOfParent => SyncStatus::PendingPush,
10 SubmoduleStatus::BehindRemote => SyncStatus::PendingPull,
11 _ => SyncStatus::Conflict,
12 }
13}
14
15pub fn status(root: PathBuf, offline: bool) -> Result<StatusReport, String> {
16 let state = if offline {
17 RepoState::scan_offline(&root)
18 } else {
19 RepoState::scan(&root)
20 }
21 .map_err(|e| format!("扫描失败: {}", e))?;
22
23 let mut components = Vec::with_capacity(state.submodules.len());
24 for sm in &state.submodules {
25 let s = map_status(&sm.status);
26 components.push(ComponentStatus {
27 name: sm.name.clone(),
28 status: s,
29 ahead: sm.ahead_count,
30 behind: sm.behind_count,
31 });
32 }
33
34 let total = components.len();
35 let synced = components
36 .iter()
37 .filter(|c| c.status == SyncStatus::Synced)
38 .count();
39 let pending = total - synced;
40
41 Ok(StatusReport {
42 root: state.root_path.to_string_lossy().to_string(),
43 components,
44 total,
45 synced,
46 pending,
47 })
48}
49
50#[cfg(test)]
51mod tests {
52 fn git_init(path: &std::path::Path) {
53 let repo = git2::Repository::init(path).unwrap();
54 let mut cfg = repo.config().unwrap();
55 cfg.set_str("user.email", "t@t").unwrap();
56 cfg.set_str("user.name", "t").unwrap();
57 }
58
59 fn git_commit(path: &std::path::Path, msg: &str) {
60 std::fs::write(path.join("f"), msg).unwrap();
61 let repo = git2::Repository::open(path).unwrap();
62 let mut index = repo.index().unwrap();
63 index.add_path(std::path::Path::new("f")).unwrap();
64 index.write().unwrap();
65 let tree_id = index.write_tree().unwrap();
66 let tree = repo.find_tree(tree_id).unwrap();
67 let sig = repo.signature().unwrap();
68 let parent = repo.head().and_then(|h| h.peel_to_commit()).ok();
69 let parents: Vec<&git2::Commit> = parent.iter().collect();
70 repo.commit(Some("HEAD"), &sig, &sig, msg, &tree, &parents).unwrap();
71 }
72
73 use super::*;
74 use crate::git::SubmoduleStatus;
75
76 #[test]
79 fn test_map_clean() {
80 assert_eq!(map_status(&SubmoduleStatus::Clean), SyncStatus::Synced);
81 }
82 #[test]
83 fn test_map_ahead() {
84 assert_eq!(
85 map_status(&SubmoduleStatus::AheadOfParent),
86 SyncStatus::PendingPush
87 );
88 }
89 #[test]
90 fn test_map_behind() {
91 assert_eq!(
92 map_status(&SubmoduleStatus::BehindRemote),
93 SyncStatus::PendingPull
94 );
95 }
96 #[test]
97 fn test_map_detached() {
98 assert_eq!(map_status(&SubmoduleStatus::Detached), SyncStatus::Conflict);
99 }
100 #[test]
101 fn test_map_dirty() {
102 assert_eq!(map_status(&SubmoduleStatus::Dirty), SyncStatus::Conflict);
103 }
104 #[test]
105 fn test_map_orphaned() {
106 assert_eq!(map_status(&SubmoduleStatus::Orphaned), SyncStatus::Conflict);
107 }
108 #[test]
109 fn test_map_uninitialized() {
110 assert_eq!(
111 map_status(&SubmoduleStatus::Uninitialized),
112 SyncStatus::Conflict
113 );
114 }
115
116 #[test]
121 fn test_status_non_git_dir() {
122 let d = tempfile::tempdir().unwrap();
123 assert!(status(d.path().to_path_buf(), false).is_err());
124 }
125
126 #[test]
127 fn test_status_empty_repo() {
128 let d = tempfile::tempdir().unwrap();
129 git_init(d.path());
130 git_commit(d.path(), "init");
131 let report = status(d.path().to_path_buf(), false).unwrap();
132 assert_eq!(report.total, 0);
133 assert_eq!(report.synced, 0);
134 assert_eq!(report.pending, 0);
135 }
136
137 #[test]
138 fn test_status_with_synced_submodule() {
139 let tmp = tempfile::tempdir().unwrap();
140 let parent = tmp.path().join("parent");
141 let sub = tmp.path().join("sub");
142 std::fs::create_dir_all(&sub).unwrap();
143 git_init(&sub);
144 git_commit(&sub, "init sub");
145 std::fs::create_dir_all(&parent).unwrap();
146 git_init(&parent);
147 git_commit(&parent, "init parent");
148 std::process::Command::new("git")
149 .args(["submodule", "add", &sub.to_string_lossy(), "libs/sub"])
150 .current_dir(&parent)
151 .output()
152 .unwrap();
153 std::process::Command::new("git")
154 .args(["commit", "-m", "add submodule"])
155 .current_dir(&parent)
156 .output()
157 .unwrap();
158 let report = status(parent, false).unwrap();
159 assert_eq!(report.total, 1);
160 assert_eq!(report.components[0].status, SyncStatus::Synced);
161 }
162
163 #[test]
164 fn test_status_offline_flag() {
165 let d = tempfile::tempdir().unwrap();
166 git_init(d.path());
167 git_commit(d.path(), "init");
168 assert!(status(d.path().to_path_buf(), true).is_ok());
170 }
171}