mirror of
https://github.com/Llewellynvdm/starship.git
synced 2024-12-26 03:27:30 +00:00
perf(git_status): replace git2 in git status module with git cli (#2465)
This commit is contained in:
parent
88c3844db3
commit
0a091bd236
@ -1,5 +1,5 @@
|
||||
use git2::{Repository, Status};
|
||||
use once_cell::sync::OnceCell;
|
||||
use regex::Regex;
|
||||
|
||||
use super::{Context, Module, RootModuleConfig};
|
||||
|
||||
@ -7,6 +7,7 @@ use crate::configs::git_status::GitStatusConfig;
|
||||
use crate::context::Repo;
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::segment::Segment;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$staged$untracked";
|
||||
@ -27,7 +28,7 @@ const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$st
|
||||
/// - `✘` — A file's deletion has been added to the staging area
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let repo = context.get_repo().ok()?;
|
||||
let info = Arc::new(GitStatusInfo::load(repo));
|
||||
let info = Arc::new(GitStatusInfo::load(context, repo));
|
||||
|
||||
let mut module = context.new_module("git_status");
|
||||
let config: GitStatusConfig = GitStatusConfig::try_load(module.config);
|
||||
@ -108,59 +109,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
}
|
||||
|
||||
struct GitStatusInfo<'a> {
|
||||
context: &'a Context<'a>,
|
||||
repo: &'a Repo,
|
||||
ahead_behind: OnceCell<Option<(usize, usize)>>,
|
||||
repo_status: OnceCell<Option<RepoStatus>>,
|
||||
stashed_count: OnceCell<Option<usize>>,
|
||||
}
|
||||
|
||||
impl<'a> GitStatusInfo<'a> {
|
||||
pub fn load(repo: &'a Repo) -> Self {
|
||||
pub fn load(context: &'a Context, repo: &'a Repo) -> Self {
|
||||
Self {
|
||||
context,
|
||||
repo,
|
||||
ahead_behind: OnceCell::new(),
|
||||
repo_status: OnceCell::new(),
|
||||
stashed_count: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_branch_name(&self) -> String {
|
||||
self.repo
|
||||
.branch
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("master"))
|
||||
}
|
||||
|
||||
fn get_repository(&self) -> Option<Repository> {
|
||||
// bare repos don't have a branch name, so `repo.branch.as_ref` would return None,
|
||||
// but git treats "master" as the default branch name
|
||||
let repo_root = self.repo.root.as_ref()?;
|
||||
Repository::open(repo_root).ok()
|
||||
}
|
||||
|
||||
pub fn get_ahead_behind(&self) -> &Option<(usize, usize)> {
|
||||
self.ahead_behind.get_or_init(|| {
|
||||
let repo = self.get_repository()?;
|
||||
let branch_name = self.get_branch_name();
|
||||
|
||||
match get_ahead_behind(&repo, &branch_name) {
|
||||
Ok(ahead_behind) => Some(ahead_behind),
|
||||
Err(error) => {
|
||||
log::debug!("get_ahead_behind: {}", error);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
pub fn get_ahead_behind(&self) -> Option<(usize, usize)> {
|
||||
self.get_repo_status().map(|data| (data.ahead, data.behind))
|
||||
}
|
||||
|
||||
pub fn get_repo_status(&self) -> &Option<RepoStatus> {
|
||||
self.repo_status.get_or_init(|| {
|
||||
let mut repo = self.get_repository()?;
|
||||
let repo_root = self.repo.root.as_ref()?;
|
||||
|
||||
match get_repo_status(&mut repo) {
|
||||
Ok(repo_status) => Some(repo_status),
|
||||
Err(error) => {
|
||||
log::debug!("get_repo_status: {}", error);
|
||||
match get_repo_status(self.context, repo_root) {
|
||||
Some(repo_status) => Some(repo_status),
|
||||
None => {
|
||||
log::debug!("get_repo_status: git status execution failed");
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -169,12 +145,12 @@ impl<'a> GitStatusInfo<'a> {
|
||||
|
||||
pub fn get_stashed(&self) -> &Option<usize> {
|
||||
self.stashed_count.get_or_init(|| {
|
||||
let mut repo = self.get_repository()?;
|
||||
let repo_root = self.repo.root.as_ref()?;
|
||||
|
||||
match get_stashed_count(&mut repo) {
|
||||
Ok(stashed_count) => Some(stashed_count),
|
||||
Err(error) => {
|
||||
log::debug!("get_stashed_count: {}", error);
|
||||
match get_stashed_count(self.context, repo_root) {
|
||||
Some(stashed_count) => Some(stashed_count),
|
||||
None => {
|
||||
log::debug!("get_stashed_count: git stash execution failed");
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -207,62 +183,53 @@ impl<'a> GitStatusInfo<'a> {
|
||||
}
|
||||
|
||||
/// Gets the number of files in various git states (staged, modified, deleted, etc...)
|
||||
fn get_repo_status(repository: &mut Repository) -> Result<RepoStatus, git2::Error> {
|
||||
fn get_repo_status(context: &Context, repo_root: &PathBuf) -> Option<RepoStatus> {
|
||||
log::debug!("New repo status created");
|
||||
let mut status_options = git2::StatusOptions::new();
|
||||
|
||||
let mut repo_status = RepoStatus::default();
|
||||
let status_output = context.exec_cmd(
|
||||
"git",
|
||||
&[
|
||||
"-C",
|
||||
&repo_root.to_string_lossy(),
|
||||
"--no-optional-locks",
|
||||
"status",
|
||||
"--porcelain=2",
|
||||
"--branch",
|
||||
],
|
||||
)?;
|
||||
let statuses = status_output.stdout.lines();
|
||||
|
||||
match repository.config()?.get_entry("status.showUntrackedFiles") {
|
||||
Ok(entry) => status_options.include_untracked(entry.value() != Some("no")),
|
||||
_ => status_options.include_untracked(true),
|
||||
};
|
||||
status_options
|
||||
.renames_from_rewrites(true)
|
||||
.renames_head_to_index(true)
|
||||
.include_unmodified(true);
|
||||
statuses.for_each(|status| {
|
||||
if status.starts_with("# branch.ab ") {
|
||||
repo_status.set_ahead_behind(status);
|
||||
} else if !status.starts_with('#') {
|
||||
repo_status.add(status);
|
||||
}
|
||||
});
|
||||
|
||||
let statuses = repository.statuses(Some(&mut status_options))?;
|
||||
|
||||
if statuses.is_empty() {
|
||||
return Err(git2::Error::from_str("Repo has no status"));
|
||||
Some(repo_status)
|
||||
}
|
||||
|
||||
statuses
|
||||
.iter()
|
||||
.map(|s| s.status())
|
||||
.for_each(|status| repo_status.add(status));
|
||||
fn get_stashed_count(context: &Context, repo_root: &PathBuf) -> Option<usize> {
|
||||
let stash_output = context.exec_cmd(
|
||||
"git",
|
||||
&[
|
||||
"-C",
|
||||
&repo_root.to_string_lossy(),
|
||||
"--no-optional-locks",
|
||||
"stash",
|
||||
"list",
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(repo_status)
|
||||
}
|
||||
|
||||
fn get_stashed_count(repository: &mut Repository) -> Result<usize, git2::Error> {
|
||||
let mut count = 0;
|
||||
repository.stash_foreach(|_, _, _| {
|
||||
count += 1;
|
||||
true
|
||||
})?;
|
||||
Result::Ok(count)
|
||||
}
|
||||
|
||||
/// Compares the current branch with the branch it is tracking to determine how
|
||||
/// far ahead or behind it is in relation
|
||||
fn get_ahead_behind(
|
||||
repository: &Repository,
|
||||
branch_name: &str,
|
||||
) -> Result<(usize, usize), git2::Error> {
|
||||
let branch_object = repository.revparse_single(branch_name)?;
|
||||
let tracking_branch_name = format!("{}@{{upstream}}", branch_name);
|
||||
let tracking_object = repository.revparse_single(&tracking_branch_name)?;
|
||||
|
||||
let branch_oid = branch_object.id();
|
||||
let tracking_oid = tracking_object.id();
|
||||
|
||||
repository.graph_ahead_behind(branch_oid, tracking_oid)
|
||||
Some(stash_output.stdout.trim().lines().count())
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
struct RepoStatus {
|
||||
ahead: usize,
|
||||
behind: usize,
|
||||
conflicted: usize,
|
||||
deleted: usize,
|
||||
renamed: usize,
|
||||
@ -272,31 +239,37 @@ struct RepoStatus {
|
||||
}
|
||||
|
||||
impl RepoStatus {
|
||||
fn is_conflicted(status: Status) -> bool {
|
||||
status.is_conflicted()
|
||||
fn is_conflicted(status: &str) -> bool {
|
||||
status.starts_with("u ")
|
||||
}
|
||||
|
||||
fn is_deleted(status: Status) -> bool {
|
||||
status.is_wt_deleted() || status.is_index_deleted()
|
||||
fn is_deleted(status: &str) -> bool {
|
||||
// is_wt_deleted || is_index_deleted
|
||||
status.starts_with("1 .D") || status.starts_with("1 D")
|
||||
}
|
||||
|
||||
fn is_renamed(status: Status) -> bool {
|
||||
status.is_wt_renamed() || status.is_index_renamed()
|
||||
fn is_renamed(status: &str) -> bool {
|
||||
// is_wt_renamed || is_index_renamed
|
||||
// Potentially a copy and not a rename
|
||||
status.starts_with("2 ")
|
||||
}
|
||||
|
||||
fn is_modified(status: Status) -> bool {
|
||||
status.is_wt_modified()
|
||||
fn is_modified(status: &str) -> bool {
|
||||
// is_wt_modified
|
||||
status.starts_with("1 .M")
|
||||
}
|
||||
|
||||
fn is_staged(status: Status) -> bool {
|
||||
status.is_index_modified() || status.is_index_new()
|
||||
fn is_staged(status: &str) -> bool {
|
||||
// is_index_modified || is_index_new
|
||||
status.starts_with("1 M") || status.starts_with("1 A")
|
||||
}
|
||||
|
||||
fn is_untracked(status: Status) -> bool {
|
||||
status.is_wt_new()
|
||||
fn is_untracked(status: &str) -> bool {
|
||||
// is_wt_new
|
||||
status.starts_with("? ")
|
||||
}
|
||||
|
||||
fn add(&mut self, s: Status) {
|
||||
fn add(&mut self, s: &str) {
|
||||
self.conflicted += RepoStatus::is_conflicted(s) as usize;
|
||||
self.deleted += RepoStatus::is_deleted(s) as usize;
|
||||
self.renamed += RepoStatus::is_renamed(s) as usize;
|
||||
@ -304,6 +277,15 @@ impl RepoStatus {
|
||||
self.staged += RepoStatus::is_staged(s) as usize;
|
||||
self.untracked += RepoStatus::is_untracked(s) as usize;
|
||||
}
|
||||
|
||||
fn set_ahead_behind(&mut self, s: &str) {
|
||||
let re = Regex::new(r"branch\.ab \+([0-9]+) \-([0-9]+)").unwrap();
|
||||
|
||||
if let Some(caps) = re.captures(s) {
|
||||
self.ahead = caps.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
||||
self.behind = caps.get(2).unwrap().as_str().parse::<usize>().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_text<F>(format_str: &str, config_path: &str, mapper: F) -> Option<Vec<Segment>>
|
||||
|
Loading…
Reference in New Issue
Block a user