summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNick Young <nick@nickwb.net>2019-09-06 02:45:04 +1000
committerMatan Kushner <hello@matchai.me>2019-09-05 12:45:04 -0400
commiteb724279da21feca8438a40249d1b2d47e8ca312 (patch)
treede4642c75b612b0bc817905cf90e711846c3eee5 /src
parent4f17bae3157d8ddd82af7c630f5ba65de0e8cb90 (diff)
feat: Adds Git State module for showing "REBASING 2/3", etc. (#276)
- Adds the git_state module. - Adds git_state to the default prompt order - Updates the documentation to describe the git_state module
Diffstat (limited to 'src')
-rw-r--r--src/module.rs1
-rw-r--r--src/modules/git_state.rs166
-rw-r--r--src/modules/mod.rs2
-rw-r--r--src/print.rs1
-rw-r--r--src/utils.rs3
5 files changed, 172 insertions, 1 deletions
diff --git a/src/module.rs b/src/module.rs
index 28c309c6d..ab1783e4f 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -12,6 +12,7 @@ pub const ALL_MODULES: &[&str] = &[
"cmd_duration",
"directory",
"git_branch",
+ "git_state",
"git_status",
"golang",
"hostname",
diff --git a/src/modules/git_state.rs b/src/modules/git_state.rs
new file mode 100644
index 000000000..55f3d4c11
--- /dev/null
+++ b/src/modules/git_state.rs
@@ -0,0 +1,166 @@
+use ansi_term::Color;
+use git2::{Repository, RepositoryState};
+use std::path::Path;
+
+use super::{Context, Module};
+
+/// Creates a module with the state of the git repository at the current directory
+///
+/// During a git operation it will show: REBASING, BISECTING, MERGING, etc.
+/// If the progress information is available (e.g. rebasing 3/10), it will show that too.
+pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
+ let mut module = context.new_module("git_state")?;
+
+ let repo_root = context.repo_root.as_ref()?;
+ let mut repository = Repository::open(repo_root).ok()?;
+ let state_description = get_state_description(&mut repository);
+
+ if let StateDescription::Clean = state_description {
+ return None;
+ }
+
+ module.get_prefix().set_value("(");
+ module.get_suffix().set_value(") ");
+ module.set_style(Color::Yellow.bold());
+
+ let label = match state_description {
+ StateDescription::Label(label) => label,
+ StateDescription::LabelAndProgress(label, _) => label,
+ // Should only be possible if you've added a new variant to StateDescription
+ _ => panic!("Expected to have a label at this point in the control flow."),
+ };
+
+ module.new_segment(label.segment_name, label.message_default);
+
+ if let StateDescription::LabelAndProgress(_, progress) = state_description {
+ module.new_segment("progress_current", &format!(" {}", progress.current));
+ module.new_segment("progress_divider", "/");
+ module.new_segment("progress_total", &format!("{}", progress.total));
+ }
+
+ Some(module)
+}
+
+static MERGE_LABEL: StateLabel = StateLabel {
+ segment_name: "merge",
+ message_default: "MERGING",
+};
+
+static REVERT_LABEL: StateLabel = StateLabel {
+ segment_name: "revert",
+ message_default: "REVERTING",
+};
+
+static CHERRY_LABEL: StateLabel = StateLabel {
+ segment_name: "cherry_pick",
+ message_default: "CHERRY-PICKING",
+};
+
+static BISECT_LABEL: StateLabel = StateLabel {
+ segment_name: "bisect",
+ message_default: "BISECTING",
+};
+
+static AM_LABEL: StateLabel = StateLabel {
+ segment_name: "am",
+ message_default: "AM",
+};
+
+static REBASE_LABEL: StateLabel = StateLabel {
+ segment_name: "rebase",
+ message_default: "REBASING",
+};
+
+static AM_OR_REBASE_LABEL: StateLabel = StateLabel {
+ segment_name: "am_or_rebase",
+ message_default: "AM/REBASE",
+};
+
+fn get_state_description(repository: &mut Repository) -> StateDescription {
+ match repository.state() {
+ RepositoryState::Clean => StateDescription::Clean,
+ RepositoryState::Merge => StateDescription::Label(&MERGE_LABEL),
+ RepositoryState::Revert => StateDescription::Label(&REVERT_LABEL),
+ RepositoryState::RevertSequence => StateDescription::Label(&REVERT_LABEL),
+ RepositoryState::CherryPick => StateDescription::Label(&CHERRY_LABEL),
+ RepositoryState::CherryPickSequence => StateDescription::Label(&CHERRY_LABEL),
+ RepositoryState::Bisect => StateDescription::Label(&BISECT_LABEL),
+ RepositoryState::ApplyMailbox => StateDescription::Label(&AM_LABEL),
+ RepositoryState::ApplyMailboxOrRebase => StateDescription::Label(&AM_OR_REBASE_LABEL),
+ RepositoryState::Rebase => describe_rebase(repository),
+ RepositoryState::RebaseInteractive => describe_rebase(repository),
+ RepositoryState::RebaseMerge => describe_rebase(repository),
+ }
+}
+
+fn describe_rebase(repository: &mut Repository) -> StateDescription {
+ /*
+ * Sadly, libgit2 seems to have some issues with reading the state of
+ * interactive rebases. So, instead, we'll poke a few of the .git files
+ * ourselves. This might be worth re-visiting this in the future...
+ *
+ * The following is based heavily on: https://github.com/magicmonty/bash-git-prompt
+ */
+
+ let just_label = StateDescription::Label(&REBASE_LABEL);
+
+ let dot_git = repository
+ .workdir()
+ .and_then(|d| Some(d.join(Path::new(".git"))));
+
+ let dot_git = match dot_git {
+ None => {
+ // We didn't find the .git directory.
+ // Something very odd is going on. We'll just back away slowly.
+ return just_label;
+ }
+ Some(path) => path,
+ };
+
+ let has_path = |relative_path: &str| {
+ let path = dot_git.join(Path::new(relative_path));
+ path.exists()
+ };
+
+ let file_to_usize = |relative_path: &str| {
+ let path = dot_git.join(Path::new(relative_path));
+ let contents = crate::utils::read_file(path).ok()?;
+ let quantity = contents.trim().parse::<usize>().ok()?;
+ Some(quantity)
+ };
+
+ let paths_to_progress = |current_path: &str, total_path: &str| {
+ let current = file_to_usize(current_path)?;
+ let total = file_to_usize(total_path)?;
+ Some(StateProgress { current, total })
+ };
+
+ let progress = if has_path("rebase-merge") {
+ paths_to_progress("rebase-merge/msgnum", "rebase-merge/end")
+ } else if has_path("rebase-apply") {
+ paths_to_progress("rebase-apply/next", "rebase-apply/last")
+ } else {
+ None
+ };
+
+ match progress {
+ None => just_label,
+ Some(progress) => StateDescription::LabelAndProgress(&REBASE_LABEL, progress),
+ }
+}
+
+enum StateDescription {
+ Clean,
+ Label(&'static StateLabel),
+ LabelAndProgress(&'static StateLabel, StateProgress),
+}
+
+struct StateLabel {
+ segment_name: &'static str,
+ message_default: &'static str,
+}
+
+struct StateProgress {
+ current: usize,
+ total: usize,
+}
diff --git a/src/modules/mod.rs b/src/modules/mod.rs
index 23d82601f..83a7d1c40 100644
--- a/src/modules/mod.rs
+++ b/src/modules/mod.rs
@@ -3,6 +3,7 @@ mod character;
mod cmd_duration;
mod directory;
mod git_branch;
+mod git_state;
mod git_status;
mod golang;
mod hostname;
@@ -34,6 +35,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"line_break" => line_break::module(context),
"package" => package::module(context),
"git_branch" => git_branch::module(context),
+ "git_state" => git_state::module(context),
"git_status" => git_status::module(context),
"username" => username::module(context),
#[cfg(feature = "battery")]
diff --git a/src/print.rs b/src/print.rs
index 74997b26d..1ea1648ff 100644
--- a/src/print.rs
+++ b/src/print.rs
@@ -16,6 +16,7 @@ const DEFAULT_PROMPT_ORDER: &[&str] = &[
"hostname",
"directory",
"git_branch",
+ "git_state",
"git_status",
"package",
"nodejs",
diff --git a/src/utils.rs b/src/utils.rs
index 4a0337ae6..c873f45aa 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,8 +1,9 @@
use std::fs::File;
use std::io::{Read, Result};
+use std::path::Path;
/// Return the string contents of a file
-pub fn read_file(file_name: &str) -> Result<String> {
+pub fn read_file<P: AsRef<Path>>(file_name: P) -> Result<String> {
let mut file = File::open(file_name)?;
let mut data = String::new();