1use anyhow::*;
2use std::io::Write;
3
4use crate::{config, repo};
5
6pub fn status<W: Write>(
7 wok_config: &mut config::Config,
8 umbrella: &repo::Repo,
9 stdout: &mut W,
10 fetch: bool,
11) -> Result<()> {
12 if fetch {
14 umbrella.fetch()?;
15 for config_repo in &wok_config.repos {
16 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
17 subrepo.fetch()?;
18 }
19 }
20 }
21
22 let umbrella_clean = is_repo_clean(&umbrella.git_repo, Some(&wok_config.repos))?;
24 let umbrella_emoji = if umbrella_clean { "✓" } else { "✗" };
25 let clean_status = if umbrella_clean { "all clean" } else { "dirty" };
26
27 let remote_status = get_remote_status_string(umbrella, &umbrella.head)?;
29
30 writeln!(
31 stdout,
32 "{} (umbrella) on branch '{}', {}{}",
33 umbrella_emoji, &umbrella.head, clean_status, remote_status
34 )?;
35
36 for config_repo in &wok_config.repos {
38 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
39 let subrepo_clean = is_repo_clean(&subrepo.git_repo, None)?;
40 let subrepo_emoji = if subrepo_clean { "✓" } else { "✗" };
41 let subrepo_clean_status =
42 if subrepo_clean { "all clean" } else { "dirty" };
43 let subrepo_remote_status =
44 get_remote_status_string(subrepo, &subrepo.head)?;
45
46 writeln!(
47 stdout,
48 "{} '{}' on branch '{}', {}{}",
49 subrepo_emoji,
50 config_repo.path.display(),
51 &subrepo.head,
52 subrepo_clean_status,
53 subrepo_remote_status
54 )?;
55 }
56 }
57
58 Ok(())
59}
60
61fn get_remote_status_string(
62 repo_obj: &repo::Repo,
63 branch_name: &str,
64) -> Result<String> {
65 let tracking = match repo_obj.tracking_branch(branch_name)? {
66 Some(tracking) => tracking,
67 None => return Ok(String::new()), };
69
70 match repo_obj.get_remote_comparison(branch_name)? {
71 Some(repo::RemoteComparison::UpToDate) => Ok(format!(
72 ", up to date with '{}'",
73 tracking.remote_ref.replace("refs/remotes/", "")
74 )),
75 Some(repo::RemoteComparison::Ahead(count)) => {
76 let commits = if count == 1 { "commit" } else { "commits" };
77 Ok(format!(
78 ", ahead of '{}' by {} {}",
79 tracking.remote_ref.replace("refs/remotes/", ""),
80 count,
81 commits
82 ))
83 },
84 Some(repo::RemoteComparison::Behind(count)) => {
85 let commits = if count == 1 { "commit" } else { "commits" };
86 Ok(format!(
87 ", behind '{}' by {} {}",
88 tracking.remote_ref.replace("refs/remotes/", ""),
89 count,
90 commits
91 ))
92 },
93 Some(repo::RemoteComparison::Diverged(ahead, behind)) => Ok(format!(
94 ", diverged from '{}' ({} ahead, {} behind)",
95 tracking.remote_ref.replace("refs/remotes/", ""),
96 ahead,
97 behind
98 )),
99 Some(repo::RemoteComparison::NoRemote) => {
100 Ok(String::new()) },
102 None => Ok(String::new()), }
104}
105
106fn is_repo_clean(
107 git_repo: &git2::Repository,
108 config_repos: Option<&[crate::config::Repo]>,
109) -> Result<bool> {
110 let mut status_options = git2::StatusOptions::new();
112 status_options.include_ignored(false);
113 status_options.include_untracked(true);
114
115 let statuses = git_repo.statuses(Some(&mut status_options))?;
116
117 for entry in statuses.iter() {
119 let status = entry.status();
120 let path = entry.path();
121
122 if status == git2::Status::WT_NEW && path == Some("wok.toml") {
124 continue;
125 }
126
127 if status == git2::Status::INDEX_NEW && path == Some(".gitmodules") {
129 continue;
130 }
131
132 if status == git2::Status::INDEX_NEW
134 && let Some(path_str) = path
135 && let Some(config_repos) = config_repos
136 && config_repos
137 .iter()
138 .any(|r| r.path.to_string_lossy() == path_str)
139 {
140 continue;
141 }
142
143 return Ok(false);
145 }
146
147 Ok(true)
148}