2023-02-21 04:03:17 +00:00
|
|
|
use gix::state::InProgress;
|
2021-12-03 20:15:31 +00:00
|
|
|
use std::path::PathBuf;
|
2019-09-05 16:45:04 +00:00
|
|
|
|
2022-03-26 09:42:19 +00:00
|
|
|
use super::{Context, Module, ModuleConfig};
|
2019-11-06 13:00:31 +00:00
|
|
|
use crate::configs::git_state::GitStateConfig;
|
2021-12-03 20:15:31 +00:00
|
|
|
use crate::context::Repo;
|
2020-07-07 22:45:32 +00:00
|
|
|
use crate::formatter::StringFormatter;
|
2019-09-05 16:45:04 +00:00
|
|
|
|
|
|
|
/// 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>> {
|
2019-09-09 23:14:38 +00:00
|
|
|
let mut module = context.new_module("git_state");
|
2019-11-06 13:00:31 +00:00
|
|
|
let config: GitStateConfig = GitStateConfig::try_load(module.config);
|
|
|
|
|
2019-09-09 23:14:38 +00:00
|
|
|
let repo = context.get_repo().ok()?;
|
2019-09-05 16:45:04 +00:00
|
|
|
|
2021-12-03 20:15:31 +00:00
|
|
|
let state_description = get_state_description(repo, &config)?;
|
2020-07-07 22:45:32 +00:00
|
|
|
|
|
|
|
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
|
|
|
formatter
|
|
|
|
.map_meta(|variable, _| match variable {
|
|
|
|
"state" => Some(state_description.label),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.map_style(|variable| match variable {
|
|
|
|
"style" => Some(Ok(config.style)),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.map(|variable| match variable {
|
|
|
|
"progress_current" => state_description.current.as_ref().map(Ok),
|
|
|
|
"progress_total" => state_description.total.as_ref().map(Ok),
|
|
|
|
_ => None,
|
|
|
|
})
|
2021-11-01 21:18:45 +00:00
|
|
|
.parse(None, Some(context))
|
2020-07-07 22:45:32 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
module.set_segments(match parsed {
|
|
|
|
Ok(segments) => segments,
|
|
|
|
Err(error) => {
|
|
|
|
log::warn!("Error in module `git_state`:\n{}", error);
|
2019-11-06 13:00:31 +00:00
|
|
|
return None;
|
|
|
|
}
|
2020-07-07 22:45:32 +00:00
|
|
|
});
|
2019-09-05 16:45:04 +00:00
|
|
|
|
|
|
|
Some(module)
|
|
|
|
}
|
|
|
|
|
2019-09-09 23:14:38 +00:00
|
|
|
/// Returns the state of the current repository
|
|
|
|
///
|
|
|
|
/// During a git operation it will show: REBASING, BISECTING, MERGING, etc.
|
2019-11-06 13:00:31 +00:00
|
|
|
fn get_state_description<'a>(
|
2021-12-03 20:15:31 +00:00
|
|
|
repo: &'a Repo,
|
2020-07-07 22:45:32 +00:00
|
|
|
config: &GitStateConfig<'a>,
|
|
|
|
) -> Option<StateDescription<'a>> {
|
2022-08-09 02:33:00 +00:00
|
|
|
match repo.state.as_ref()? {
|
|
|
|
InProgress::Merge => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.merge,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::Revert => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.revert,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::RevertSequence => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.revert,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::CherryPick => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.cherry_pick,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::CherryPickSequence => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.cherry_pick,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::Bisect => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.bisect,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::ApplyMailbox => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.am,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::ApplyMailboxRebase => Some(StateDescription {
|
2020-07-07 22:45:32 +00:00
|
|
|
label: config.am_or_rebase,
|
|
|
|
current: None,
|
|
|
|
total: None,
|
|
|
|
}),
|
2022-08-09 02:33:00 +00:00
|
|
|
InProgress::Rebase => Some(describe_rebase(repo, config.rebase)),
|
|
|
|
InProgress::RebaseInteractive => Some(describe_rebase(repo, config.rebase)),
|
2019-09-05 16:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-09 02:33:00 +00:00
|
|
|
// TODO: Use future gitoxide API to get the state of the rebase
|
2021-12-03 20:15:31 +00:00
|
|
|
fn describe_rebase<'a>(repo: &'a Repo, rebase_config: &'a str) -> StateDescription<'a> {
|
2019-09-05 16:45:04 +00:00
|
|
|
/*
|
|
|
|
* 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 has_path = |relative_path: &str| {
|
2021-12-03 20:15:31 +00:00
|
|
|
let path = repo.path.join(PathBuf::from(relative_path));
|
2019-09-05 16:45:04 +00:00
|
|
|
path.exists()
|
|
|
|
};
|
|
|
|
|
|
|
|
let file_to_usize = |relative_path: &str| {
|
2021-12-03 20:15:31 +00:00
|
|
|
let path = repo.path.join(PathBuf::from(relative_path));
|
2019-09-05 16:45:04 +00:00
|
|
|
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)?;
|
2020-07-07 22:45:32 +00:00
|
|
|
Some((current, total))
|
2019-09-05 16:45:04 +00:00
|
|
|
};
|
|
|
|
|
2020-08-14 17:09:01 +00:00
|
|
|
let progress = if has_path("rebase-merge/msgnum") {
|
2019-09-05 16:45:04 +00:00
|
|
|
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
|
|
|
|
};
|
2020-11-26 18:56:18 +00:00
|
|
|
|
|
|
|
let (current, total) = if let Some((c, t)) = progress {
|
2022-11-05 11:40:46 +00:00
|
|
|
(Some(format!("{c}")), Some(format!("{t}")))
|
2020-11-26 18:56:18 +00:00
|
|
|
} else {
|
|
|
|
(None, None)
|
|
|
|
};
|
2019-09-05 16:45:04 +00:00
|
|
|
|
2020-07-07 22:45:32 +00:00
|
|
|
StateDescription {
|
|
|
|
label: rebase_config,
|
2020-11-26 18:56:18 +00:00
|
|
|
current,
|
|
|
|
total,
|
2019-09-05 16:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-07 22:45:32 +00:00
|
|
|
struct StateDescription<'a> {
|
|
|
|
label: &'a str,
|
|
|
|
current: Option<String>,
|
|
|
|
total: Option<String>,
|
2019-11-06 13:00:31 +00:00
|
|
|
}
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-09-04 16:44:54 +00:00
|
|
|
use nu_ansi_term::Color;
|
2020-08-07 19:13:12 +00:00
|
|
|
use std::ffi::OsStr;
|
2022-11-15 10:14:52 +00:00
|
|
|
use std::io::{self, Error, ErrorKind};
|
2020-08-07 19:13:12 +00:00
|
|
|
use std::path::Path;
|
2021-07-16 19:20:59 +00:00
|
|
|
use std::process::Stdio;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
use crate::test::ModuleRenderer;
|
2022-11-15 10:14:52 +00:00
|
|
|
use crate::utils::{create_command, write_file};
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn show_nothing_on_empty_dir() -> io::Result<()> {
|
|
|
|
let repo_dir = tempfile::tempdir()?;
|
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state")
|
|
|
|
.path(repo_dir.path())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let expected = None;
|
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_rebasing() -> io::Result<()> {
|
|
|
|
let repo_dir = create_repo_with_conflict()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["rebase", "other-branch"], Some(path), false)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
|
|
|
|
2020-09-21 19:17:06 +00:00
|
|
|
let expected = Some(format!("({}) ", Color::Yellow.bold().paint("REBASING 1/1")));
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_merging() -> io::Result<()> {
|
|
|
|
let repo_dir = create_repo_with_conflict()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["merge", "other-branch"], Some(path), false)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
|
|
|
|
2020-09-21 19:17:06 +00:00
|
|
|
let expected = Some(format!("({}) ", Color::Yellow.bold().paint("MERGING")));
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_cherry_picking() -> io::Result<()> {
|
|
|
|
let repo_dir = create_repo_with_conflict()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["cherry-pick", "other-branch"], Some(path), false)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
|
|
|
|
|
|
|
let expected = Some(format!(
|
2020-09-21 19:17:06 +00:00
|
|
|
"({}) ",
|
|
|
|
Color::Yellow.bold().paint("CHERRY-PICKING")
|
2020-08-07 19:13:12 +00:00
|
|
|
));
|
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_bisecting() -> io::Result<()> {
|
|
|
|
let repo_dir = create_repo_with_conflict()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["bisect", "start"], Some(path), false)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
|
|
|
|
2020-09-21 19:17:06 +00:00
|
|
|
let expected = Some(format!("({}) ", Color::Yellow.bold().paint("BISECTING")));
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_reverting() -> io::Result<()> {
|
|
|
|
let repo_dir = create_repo_with_conflict()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["revert", "--no-commit", "HEAD~1"], Some(path), false)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
|
|
|
|
2020-09-21 19:17:06 +00:00
|
|
|
let expected = Some(format!("({}) ", Color::Yellow.bold().paint("REVERTING")));
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
repo_dir.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_git_cmd<A, S>(args: A, dir: Option<&Path>, should_succeed: bool) -> io::Result<()>
|
|
|
|
where
|
|
|
|
A: IntoIterator<Item = S>,
|
|
|
|
S: AsRef<OsStr>,
|
|
|
|
{
|
2021-07-16 19:20:59 +00:00
|
|
|
let mut command = create_command("git")?;
|
2020-08-07 19:13:12 +00:00
|
|
|
command
|
|
|
|
.args(args)
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.stdin(Stdio::null());
|
|
|
|
|
|
|
|
if let Some(dir) = dir {
|
|
|
|
command.current_dir(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = command.status()?;
|
|
|
|
|
|
|
|
if should_succeed && !status.success() {
|
|
|
|
Err(Error::from(ErrorKind::Other))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_repo_with_conflict() -> io::Result<tempfile::TempDir> {
|
|
|
|
let repo_dir = tempfile::tempdir()?;
|
|
|
|
let path = repo_dir.path();
|
|
|
|
let conflicted_file = repo_dir.path().join("the_file");
|
|
|
|
|
|
|
|
// Initialize a new git repo
|
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
[
|
2020-08-07 19:13:12 +00:00
|
|
|
"init",
|
|
|
|
"--quiet",
|
|
|
|
path.to_str().expect("Path was not UTF-8"),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Set local author info
|
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["config", "--local", "user.email", "starship@example.com"],
|
2020-08-07 19:13:12 +00:00
|
|
|
Some(path),
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["config", "--local", "user.name", "starship"],
|
2020-08-07 19:13:12 +00:00
|
|
|
Some(path),
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
|
2020-10-01 17:08:31 +00:00
|
|
|
// Ensure on the expected branch.
|
|
|
|
// If build environment has `init.defaultBranch` global set
|
2021-07-10 20:54:34 +00:00
|
|
|
// it will default to an unknown branch, so need to make & change branch
|
2020-10-01 17:08:31 +00:00
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["checkout", "-b", "master"],
|
2020-10-01 17:08:31 +00:00
|
|
|
Some(path),
|
|
|
|
// command expected to fail if already on the expected branch
|
|
|
|
false,
|
|
|
|
)?;
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
// Write a file on master and commit it
|
2022-11-15 10:14:52 +00:00
|
|
|
write_file(&conflicted_file, "Version A")?;
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["add", "the_file"], Some(path), true)?;
|
2020-10-03 10:22:19 +00:00
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["commit", "--message", "Commit A", "--no-gpg-sign"],
|
2020-10-03 10:22:19 +00:00
|
|
|
Some(path),
|
|
|
|
true,
|
|
|
|
)?;
|
2020-08-07 19:13:12 +00:00
|
|
|
|
|
|
|
// Switch to another branch, and commit a change to the file
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["checkout", "-b", "other-branch"], Some(path), true)?;
|
2022-11-15 10:14:52 +00:00
|
|
|
write_file(&conflicted_file, "Version B")?;
|
2020-08-07 19:13:12 +00:00
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["commit", "--all", "--message", "Commit B", "--no-gpg-sign"],
|
2020-08-07 19:13:12 +00:00
|
|
|
Some(path),
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Switch back to master, and commit a third change to the file
|
2022-11-05 11:40:46 +00:00
|
|
|
run_git_cmd(["checkout", "master"], Some(path), true)?;
|
2022-11-15 10:14:52 +00:00
|
|
|
write_file(conflicted_file, "Version C")?;
|
2020-08-07 19:13:12 +00:00
|
|
|
run_git_cmd(
|
2022-11-05 11:40:46 +00:00
|
|
|
["commit", "--all", "--message", "Commit C", "--no-gpg-sign"],
|
2020-08-07 19:13:12 +00:00
|
|
|
Some(path),
|
|
|
|
true,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(repo_dir)
|
|
|
|
}
|
|
|
|
}
|