use crate::{
action::{parallel, serial, task_vec, ActionTask},
select::{Entry, State},
version_control_actions::{handle_command, task, VersionControlActions},
};
fn str_to_state(s: &str) -> State {
match s {
"?" => State::Untracked,
"M" => State::Modified,
"A" => State::Added,
"D" => State::Deleted,
"R" => State::Renamed,
"C" => State::Copied,
"U" => State::Unmerged,
_ => State::Unmodified,
}
}
pub struct GitActions {
pub current_dir: String,
}
impl VersionControlActions for GitActions {
fn executable_name(&self) -> &'static str {
"git"
}
fn current_dir(&self) -> &str {
&self.current_dir[..]
}
fn set_root(&mut self) -> Result<(), String> {
let mut command = self.command();
let dir =
handle_command(command.args(&["rev-parse", "--show-toplevel"]))?;
let dir = dir
.lines()
.next()
.expect("root directory is an empty string");
self.current_dir = dir.to_owned();
Ok(())
}
fn get_root(&self) -> &str {
&self.current_dir[..]
}
fn get_current_changed_files(&self) -> Result<Vec<Entry>, String> {
let output = handle_command(self.command().args(&["status", "-z"]))?;
let files = output
.trim()
.split('\0')
.map(|e| e.trim())
.filter(|e| e.len() > 2)
.map(|e| {
let (state, filename) = e.split_at(2);
Entry {
filename: String::from(filename.trim()),
selected: false,
state: str_to_state(&state[..1]),
}
})
.collect();
Ok(files)
}
fn get_revision_changed_files(
&self,
target: &str,
) -> Result<Vec<Entry>, String> {
let output = handle_command(
self.command()
.arg("diff-tree")
.arg("--no-commit-id")
.arg("--name-status")
.arg("-z")
.arg("-r")
.arg(target),
)?;
let state_iter = output.split('\0').map(|e| e.trim()).step_by(2);
let filename_iter =
output.split('\0').map(|e| e.trim()).skip(1).step_by(2);
let files = state_iter
.zip(filename_iter)
.map(|(s, f)| Entry {
filename: String::from(f),
selected: false,
state: str_to_state(s),
})
.collect();
Ok(files)
}
fn version(&self) -> Result<String, String> {
handle_command(self.command().arg("--version"))
}
fn status(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["-c", "color.status=always", "status"]);
})
}
fn current_export(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["show", "--color"]);
})
}
fn log(&self, count: usize) -> Box<dyn ActionTask> {
task(self, |command| {
let count_str = format!("-{}", count);
let template =
"--format=format:%x1e%h%x1e%as%x1e%<(10,trunc)%aN%x1e%D%x1e%s";
command
.arg("log")
.arg("--all")
.arg("--decorate")
.arg("--oneline")
.arg("--graph")
.arg(&count_str)
.arg(template);
})
}
fn current_diff_all(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["diff", "--color"]);
})
}
fn current_diff_selected(
&self,
entries: &Vec<Entry>,
) -> Box<dyn ActionTask> {
task(self, |command| {
command.arg("diff").arg("--color").arg("--");
for e in entries.iter().filter(|e| e.selected) {
command.arg(&e.filename);
}
})
}
fn revision_changes(&self, target: &str) -> Box<dyn ActionTask> {
task(self, |command| {
command
.arg("diff-tree")
.arg("--no-commit-id")
.arg("--name-status")
.arg("-r")
.arg(target)
.arg("--color");
})
}
fn revision_diff_all(&self, target: &str) -> Box<dyn ActionTask> {
task(self, |command| {
let mut parents = String::from(target);
parents.push_str("^@");
command.arg("diff").arg(parents).arg(target).arg("--color");
})
}
fn revision_diff_selected(
&self,
target: &str,
entries: &Vec<Entry>,
) -> Box<dyn ActionTask> {
task(self, |command| {
let mut parents = String::from(target);
parents.push_str("^@");
command
.arg("diff")
.arg("--color")
.arg(parents)
.arg(target)
.arg("--");
for e in entries.iter().filter(|e| e.selected) {
command.arg(&e.filename);
}
})
}
fn commit_all(&self, message: &str) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
tasks.push(task(self, |command| {
command.args(&["add", "--all"]);
}));
tasks.push(task(self, |command| {
command.arg("commit").arg("-m").arg(message);
}));
serial(tasks)
}
fn commit_selected(
&self,
message: &str,
entries: &Vec<Entry>,
) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
for e in entries.iter().filter(|e| e.selected) {
tasks.push(task(self, |command| {
command.arg("add").arg("--").arg(&e.filename);
}));
}
tasks.push(task(self, |command| {
command.arg("commit").arg("-m").arg(message);
}));
serial(tasks)
}
fn revert_all(&self) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
tasks.push(task(self, |command| {
command.args(&["reset", "--hard"]);
}));
tasks.push(task(self, |command| {
command.args(&["clean", "-df"]);
}));
serial(tasks)
}
fn revert_selected(&self, entries: &Vec<Entry>) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
for e in entries.iter().filter(|e| e.selected) {
match e.state {
State::Untracked => {
tasks.push(task(self, |command| {
command
.arg("clean")
.arg("-f")
.arg("--")
.arg(&e.filename);
}));
}
State::Added => {
tasks.push(task(self, |command| {
command.arg("rm").arg("-f").arg("--").arg(&e.filename);
}));
}
_ => {
tasks.push(task(self, |command| {
command.arg("checkout").arg("--").arg(&e.filename);
}));
}
}
}
parallel(tasks)
}
fn update(&self, target: &str) -> Box<dyn ActionTask> {
task(self, |command| {
command.arg("checkout").arg(target);
})
}
fn merge(&self, target: &str) -> Box<dyn ActionTask> {
task(self, |command| {
command.arg("merge").arg(target);
})
}
fn conflicts(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["diff", "--name-only", "--diff-filter=U"]);
})
}
fn take_other(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["checkout", ".", "--theirs"]);
})
}
fn take_local(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["checkout", ".", "--ours"]);
})
}
fn fetch(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["fetch", "--all"]);
})
}
fn pull(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["pull", "--all"]);
})
}
fn push(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.arg("push");
})
}
fn create_tag(&self, name: &str) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
tasks.push(task(self, |command| {
command.arg("tag").arg(name).arg("-f");
}));
tasks.push(task(self, |command| {
command.arg("push").arg("origin").arg(name);
}));
serial(tasks)
}
fn list_branches(&self) -> Box<dyn ActionTask> {
task(self, |command| {
command.args(&["branch", "--all", "--format=%(refname:short)"]);
})
}
fn create_branch(&self, name: &str) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
tasks.push(task(self, |command| {
command.arg("branch").arg(name);
}));
tasks.push(self.update(name));
tasks.push(task(self, |command| {
command
.arg("push")
.arg("--set-upstream")
.arg("origin")
.arg(name);
}));
serial(tasks)
}
fn close_branch(&self, name: &str) -> Box<dyn ActionTask> {
let mut tasks = task_vec();
tasks.push(task(self, |command| {
command.arg("branch").arg("-d").arg(name);
}));
tasks.push(task(self, |command| {
command.arg("push").arg("-d").arg("origin").arg(name);
}));
serial(tasks)
}
}