perf(git_status): tweak exec flags to omit unnecessary info (#3287)

* perf(git_status): tweak flags to omit extra info

`git status` can be prohibitively slow on some repos, so allow the
config to influence what flags are passed to git. For instance, if there
is no configured symbol for untracked files, tell git to omit them from
its output. This can easily result in a 2~10x speedup in many cases, but
requires the user to opt-in to hiding information from the prompt.

* docs(git_status): add ignore_submodules option
This commit is contained in:
arcnmx 2021-12-30 13:12:53 -08:00 committed by GitHub
parent 1c305c9de7
commit a95332485b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 53 additions and 32 deletions

View File

@ -1407,22 +1407,23 @@ current directory.
### Options
| Option | Default | Description |
| ------------ | --------------------------------------------- | ----------------------------------- |
| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` |
| `conflicted` | `"="` | This branch has merge conflicts. |
| `ahead` | `"⇡"` | The format of `ahead` |
| `behind` | `"⇣"` | The format of `behind` |
| `diverged` | `"⇕"` | The format of `diverged` |
| `up_to_date` | `""` | The format of `up_to_date` |
| `untracked` | `"?"` | The format of `untracked` |
| `stashed` | `"$"` | The format of `stashed` |
| `modified` | `"!"` | The format of `modified` |
| `staged` | `"+"` | The format of `staged` |
| `renamed` | `"»"` | The format of `renamed` |
| `deleted` | `"✘"` | The format of `deleted` |
| `style` | `"bold red"` | The style for the module. |
| `disabled` | `false` | Disables the `git_status` module. |
| Option | Default | Description |
| ------------------- | --------------------------------------------- | ----------------------------------- |
| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` |
| `conflicted` | `"="` | This branch has merge conflicts. |
| `ahead` | `"⇡"` | The format of `ahead` |
| `behind` | `"⇣"` | The format of `behind` |
| `diverged` | `"⇕"` | The format of `diverged` |
| `up_to_date` | `""` | The format of `up_to_date` |
| `untracked` | `"?"` | The format of `untracked` |
| `stashed` | `"$"` | The format of `stashed` |
| `modified` | `"!"` | The format of `modified` |
| `staged` | `"+"` | The format of `staged` |
| `renamed` | `"»"` | The format of `renamed` |
| `deleted` | `"✘"` | The format of `deleted` |
| `style` | `"bold red"` | The style for the module. |
| `ignore_submodules` | `false` | Ignore changes to submodules. |
| `disabled` | `false` | Disables the `git_status` module. |
### Variables

View File

@ -18,6 +18,7 @@ pub struct GitStatusConfig<'a> {
pub modified: &'a str,
pub staged: &'a str,
pub untracked: &'a str,
pub ignore_submodules: bool,
pub disabled: bool,
}
@ -37,6 +38,7 @@ impl<'a> Default for GitStatusConfig<'a> {
modified: "!",
staged: "+",
untracked: "?",
ignore_submodules: false,
disabled: false,
}
}

View File

@ -27,11 +27,11 @@ const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$st
/// - `»` — A renamed file has been added to the staging area
/// - `✘` — A file's deletion has been added to the staging area
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let info = Arc::new(GitStatusInfo::load(context));
let mut module = context.new_module("git_status");
let config: GitStatusConfig = GitStatusConfig::try_load(module.config);
let info = Arc::new(GitStatusInfo::load(context, config.clone()));
//Return None if not in git repository
context.get_repo().ok()?;
@ -116,14 +116,16 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
struct GitStatusInfo<'a> {
context: &'a Context<'a>,
config: GitStatusConfig<'a>,
repo_status: OnceCell<Option<RepoStatus>>,
stashed_count: OnceCell<Option<usize>>,
}
impl<'a> GitStatusInfo<'a> {
pub fn load(context: &'a Context) -> Self {
pub fn load(context: &'a Context, config: GitStatusConfig<'a>) -> Self {
Self {
context,
config,
repo_status: OnceCell::new(),
stashed_count: OnceCell::new(),
}
@ -135,7 +137,7 @@ impl<'a> GitStatusInfo<'a> {
pub fn get_repo_status(&self) -> &Option<RepoStatus> {
self.repo_status
.get_or_init(|| match get_repo_status(self.context) {
.get_or_init(|| match get_repo_status(self.context, &self.config) {
Some(repo_status) => Some(repo_status),
None => {
log::debug!("get_repo_status: git status execution failed");
@ -181,21 +183,37 @@ impl<'a> GitStatusInfo<'a> {
}
/// Gets the number of files in various git states (staged, modified, deleted, etc...)
fn get_repo_status(context: &Context) -> Option<RepoStatus> {
fn get_repo_status(context: &Context, config: &GitStatusConfig) -> Option<RepoStatus> {
log::debug!("New repo status created");
let mut repo_status = RepoStatus::default();
let status_output = context.exec_cmd(
"git",
&[
OsStr::new("-C"),
context.current_dir.as_os_str(),
OsStr::new("--no-optional-locks"),
OsStr::new("status"),
OsStr::new("--porcelain=2"),
OsStr::new("--branch"),
],
)?;
let mut args = vec![
OsStr::new("-C"),
context.current_dir.as_os_str(),
OsStr::new("--no-optional-locks"),
OsStr::new("status"),
OsStr::new("--porcelain=2"),
];
// for performance reasons, only pass flags if necessary...
let has_ahead_behind = !config.ahead.is_empty() || !config.behind.is_empty();
let has_up_to_date_diverged = !config.up_to_date.is_empty() || !config.diverged.is_empty();
if has_ahead_behind || has_up_to_date_diverged {
args.push(OsStr::new("--branch"));
}
// ... and add flags that omit information the user doesn't want
let has_untracked = !config.untracked.is_empty();
if !has_untracked {
args.push(OsStr::new("--untracked-files=no"));
}
if config.ignore_submodules {
args.push(OsStr::new("--ignore-submodules=dirty"));
} else if !has_untracked {
args.push(OsStr::new("--ignore-submodules=untracked"));
}
let status_output = context.exec_cmd("git", &args)?;
let statuses = status_output.stdout.lines();
statuses.for_each(|status| {