use crate::cli::UI;
use anyhow::Result;
use git2::StatusOptions;
use serde_json::json;
use std::path::Path;
pub fn execute(path: &Path, short: bool, verbose: bool, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let head = repo.head().ok();
let branch_name = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.unwrap_or_else(|| "(detached)".to_string());
if !short {
ui.info(format!("On branch {}", branch_name));
ui.blank();
}
let mut opts = StatusOptions::new();
opts.include_untracked(true)
.recurse_untracked_dirs(true)
.renames_head_to_index(true);
let statuses = repo.statuses(Some(&mut opts))?;
if statuses.is_empty() {
if !short {
ui.success("nothing to commit, working tree clean");
}
return Ok(());
}
let mut staged = Vec::new();
let mut unstaged = Vec::new();
let mut untracked = Vec::new();
for entry in statuses.iter() {
let status = entry.status();
let path_str = entry.path().unwrap_or("").to_string();
if status.intersects(
git2::Status::INDEX_NEW
| git2::Status::INDEX_MODIFIED
| git2::Status::INDEX_DELETED
| git2::Status::INDEX_RENAMED
| git2::Status::INDEX_TYPECHANGE,
) {
let prefix = if status.contains(git2::Status::INDEX_NEW) {
"new file"
} else if status.contains(git2::Status::INDEX_MODIFIED) {
"modified"
} else if status.contains(git2::Status::INDEX_DELETED) {
"deleted"
} else if status.contains(git2::Status::INDEX_RENAMED) {
"renamed"
} else {
"typechange"
};
staged.push((prefix, path_str.clone()));
}
if status.intersects(
git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
) {
let prefix = if status.contains(git2::Status::WT_MODIFIED) {
"modified"
} else if status.contains(git2::Status::WT_DELETED) {
"deleted"
} else {
"renamed"
};
unstaged.push((prefix, path_str.clone()));
}
if status.contains(git2::Status::WT_NEW) {
untracked.push(path_str);
}
}
if short {
for (prefix, path) in &staged {
let code = match *prefix {
"new file" => "A",
"modified" => "M",
"deleted" => "D",
"renamed" => "R",
_ => "T",
};
println!("{} {}", code, path);
}
for (prefix, path) in &unstaged {
let code = match *prefix {
"modified" => " M",
"deleted" => " D",
_ => " R",
};
println!("{} {}", code, path);
}
for path in &untracked {
println!("?? {}", path);
}
return Ok(());
}
if !staged.is_empty() {
ui.section("Changes staged for commit:");
for (prefix, path) in &staged {
ui.status_item(true, format!("{:12} {}", prefix, path));
}
ui.blank();
}
if !unstaged.is_empty() {
ui.section("Changes not staged:");
for (prefix, path) in &unstaged {
ui.status_item(false, format!("{:12} {}", prefix, path));
}
ui.blank();
}
if !untracked.is_empty() {
ui.section("Untracked files:");
for path in &untracked {
ui.list_item(path);
}
ui.blank();
}
if verbose {
ui.header("Changes staged for commit:");
crate::ops::diff::execute(
path,
&crate::ops::diff::DiffDisplayOptions {
cached: true,
stat_only: false,
name_only: false,
name_status: false,
commit_spec: None,
paths: vec![],
ignore_whitespace: false,
},
ui,
)?;
ui.header("Changes not staged for commit:");
crate::ops::diff::execute(
path,
&crate::ops::diff::DiffDisplayOptions {
cached: false,
stat_only: false,
name_only: false,
name_status: false,
commit_spec: None,
paths: vec![],
ignore_whitespace: false,
},
ui,
)?;
}
Ok(())
}
pub fn execute_compact(path: &Path) -> Result<String> {
use crate::cli::compact::format_compact_status;
let repo = crate::ops::open_repo(path)?;
let head = repo.head().ok();
let branch_name = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.unwrap_or_else(|| "(detached)".to_string());
let mut opts = StatusOptions::new();
opts.include_untracked(true)
.recurse_untracked_dirs(true)
.renames_head_to_index(true);
let statuses = repo.statuses(Some(&mut opts))?;
let mut staged = Vec::new();
let mut unstaged = Vec::new();
let mut untracked = Vec::new();
for entry in statuses.iter() {
let status = entry.status();
let path_str = entry.path().unwrap_or("").to_string();
if status.intersects(
git2::Status::INDEX_NEW
| git2::Status::INDEX_MODIFIED
| git2::Status::INDEX_DELETED
| git2::Status::INDEX_RENAMED
| git2::Status::INDEX_TYPECHANGE,
) {
let prefix = if status.contains(git2::Status::INDEX_NEW) {
"new file"
} else if status.contains(git2::Status::INDEX_MODIFIED) {
"modified"
} else if status.contains(git2::Status::INDEX_DELETED) {
"deleted"
} else if status.contains(git2::Status::INDEX_RENAMED) {
"renamed"
} else {
"typechange"
};
staged.push((prefix, path_str.clone()));
}
if status.intersects(
git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
) {
let prefix = if status.contains(git2::Status::WT_MODIFIED) {
"modified"
} else if status.contains(git2::Status::WT_DELETED) {
"deleted"
} else {
"renamed"
};
unstaged.push((prefix, path_str.clone()));
}
if status.contains(git2::Status::WT_NEW) {
untracked.push(path_str);
}
}
Ok(format_compact_status(
&branch_name,
&staged,
&unstaged,
&untracked,
))
}
pub fn execute_json(path: &Path) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let head = repo.head().ok();
let branch_name = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.unwrap_or_else(|| "(detached)".to_string());
let mut opts = StatusOptions::new();
opts.include_untracked(true)
.recurse_untracked_dirs(true)
.renames_head_to_index(true);
let statuses = repo.statuses(Some(&mut opts))?;
let mut staged = Vec::new();
let mut unstaged = Vec::new();
let mut untracked = Vec::new();
for entry in statuses.iter() {
let status = entry.status();
let path_str = entry.path().unwrap_or("").to_string();
if status.intersects(
git2::Status::INDEX_NEW
| git2::Status::INDEX_MODIFIED
| git2::Status::INDEX_DELETED
| git2::Status::INDEX_RENAMED
| git2::Status::INDEX_TYPECHANGE,
) {
let kind = if status.contains(git2::Status::INDEX_NEW) {
"new"
} else if status.contains(git2::Status::INDEX_MODIFIED) {
"modified"
} else if status.contains(git2::Status::INDEX_DELETED) {
"deleted"
} else if status.contains(git2::Status::INDEX_RENAMED) {
"renamed"
} else {
"typechange"
};
staged.push(json!({"status": kind, "path": path_str}));
}
if status.intersects(
git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
) {
let kind = if status.contains(git2::Status::WT_MODIFIED) {
"modified"
} else if status.contains(git2::Status::WT_DELETED) {
"deleted"
} else {
"renamed"
};
unstaged.push(json!({"status": kind, "path": path_str}));
}
if status.contains(git2::Status::WT_NEW) {
untracked.push(path_str);
}
}
println!(
"{}",
json!({
"branch": branch_name,
"staged": staged,
"unstaged": unstaged,
"untracked": untracked,
"clean": staged.is_empty() && unstaged.is_empty() && untracked.is_empty(),
})
);
Ok(())
}