verco 1.4.0

A simple Git/Hg version control client based on keyboard shortcuts
use std::process::Command;

use crate::revision_shortcut::RevisionShortcut;
use crate::select::{Entry, State};
use crate::version_control_actions::{handle_command, VersionControlActions};

fn str_to_state(s: &str) -> State {
	match s {
		"?" => State::Untracked,
		"M" => State::Modified,
		"A" => State::Added,
		"R" => State::Deleted,
		"!" => State::Missing,
		"I" => State::Ignored,
		"C" => State::Clean,
		_ => State::Copied,
	}
}

pub struct HgActions<'a> {
	pub current_dir: &'a str,
	pub revision_shortcut: RevisionShortcut,
}

impl<'a> HgActions<'a> {
	fn command(&self) -> Command {
		let mut command = Command::new("hg");
		command.current_dir(self.current_dir);
		command
	}
}

impl<'a> VersionControlActions for HgActions<'a> {
	fn get_files_to_commit(&mut self) -> Result<Vec<Entry>, String> {
		let output = handle_command(self.command().args(&["status"]))?;

		let files: Vec<_> = output
			.trim()
			.split('\n')
			.map(|e| e.trim())
			.filter(|e| e.len() > 1)
			.map(|e| {
				let (state, filename) = e.split_at(1);
				Entry {
					filename: String::from(filename.trim()),
					selected: false,
					state: str_to_state(state),
				}
			})
			.collect();
		Ok(files)
	}

	fn version(&mut self) -> Result<String, String> {
		handle_command(self.command().arg("--version"))
	}

	fn status(&mut self) -> Result<String, String> {
		let mut output = String::new();

		output
			.push_str(&handle_command(self.command().args(&["summary", "--color", "always"]))?[..]);
		output.push_str("\n");
		output
			.push_str(&handle_command(self.command().args(&["status", "--color", "always"]))?[..]);

		Ok(output)
	}

	fn log(&mut self) -> Result<String, String> {
		let hashes_output = handle_command(self.command().args(&[
			"log",
			"--template",
			"{node|short}\n",
			"-l",
			"20",
		]))?;
		let hashes: Vec<_> = hashes_output.split_whitespace().map(String::from).collect();
		self.revision_shortcut.update_hashes(hashes);

		let mut output = handle_command(self.command().args(&[
			"log",
			"--graph",
			"--template",
			"{label(ifeq(phase, 'secret', 'yellow', ifeq(phase, 'draft', 'yellow', 'red')), node|short)}{ifeq(branch, 'default', '', label('green', ' ({branch})'))}{bookmarks % ' {bookmark}{ifeq(bookmark, active, '*')}{bookmark}'}{label('yellow', tags % ' {tag}')} {label('magenta', author|person)} {desc|firstline|strip}",
			"-l",
			"20",
			"--color",
			"always",
		]))?;

		self.revision_shortcut.replace_occurrences(&mut output);

		Ok(output)
	}

	fn changes(&mut self, target: &str) -> Result<String, String> {
		let target = self.revision_shortcut.get_hash(target).unwrap_or(target);
		handle_command(
			self.command()
				.arg("status")
				.arg("--change")
				.arg(target)
				.arg("--color")
				.arg("always"),
		)
	}

	fn diff(&mut self, target: &str) -> Result<String, String> {
		let target = self.revision_shortcut.get_hash(target).unwrap_or(target);
		handle_command(
			self.command()
				.arg("diff")
				.arg("--change")
				.arg(target)
				.arg("--color")
				.arg("always"),
		)
	}

	fn commit_all(&mut self, message: &str) -> Result<String, String> {
		handle_command(
			self.command()
				.arg("commit")
				.arg("--addremove")
				.arg("-m")
				.arg(message)
				.arg("--color")
				.arg("always"),
		)
	}

	fn commit_selected(&mut self, message: &str, entries: &Vec<Entry>) -> Result<String, String> {
		let mut cmd = self.command();
		cmd.arg("commit");

		for e in entries.iter() {
			if e.selected {
				match e.state {
					State::Missing | State::Deleted => {
						handle_command(self.command().arg("remove").arg(&e.filename))?;
					}
					State::Untracked => {
						handle_command(self.command().arg("add").arg(&e.filename))?;
					}
					_ => (),
				}

				cmd.arg(&e.filename);
			}
		}

		handle_command(cmd.arg("-m").arg(message).arg("--color").arg("always"))
	}

	fn revert_all(&mut self) -> Result<String, String> {
		let mut output = String::new();

		output.push_str(&handle_command(self.command().args(&["revert", "-C", "--all"]))?[..]);
		output.push_str("\n");
		output.push_str(&handle_command(self.command().args(&["purge"]))?[..]);

		Ok(output)
	}

	fn revert_selected(&mut self, entries: &Vec<Entry>) -> Result<String, String> {
		let mut cmd = self.command();
		cmd.arg("revert").arg("-C").arg("--color").arg("always");

		for e in entries.iter() {
			if e.selected {
				cmd.arg(&e.filename);
			}
		}

		handle_command(&mut cmd)
	}

	fn update(&mut self, target: &str) -> Result<String, String> {
		let target = self.revision_shortcut.get_hash(target).unwrap_or(target);
		handle_command(self.command().arg("update").arg(target))
	}

	fn merge(&mut self, target: &str) -> Result<String, String> {
		let target = self.revision_shortcut.get_hash(target).unwrap_or(target);
		handle_command(self.command().arg("merge").arg(target))
	}

	fn conflicts(&mut self) -> Result<String, String> {
		handle_command(self.command().args(&["resolve", "-l", "--color", "always"]))
	}

	fn take_other(&mut self) -> Result<String, String> {
		handle_command(
			self.command()
				.args(&["resolve", "-a", "-t", "internal:other"]),
		)
	}

	fn take_local(&mut self) -> Result<String, String> {
		handle_command(
			self.command()
				.args(&["resolve", "-a", "-t", "internal:local"]),
		)
	}

	fn fetch(&mut self) -> Result<String, String> {
		self.pull()
	}

	fn pull(&mut self) -> Result<String, String> {
		handle_command(self.command().arg("pull"))
	}

	fn push(&mut self) -> Result<String, String> {
		handle_command(self.command().args(&["push", "--new-branch"]))
	}

	fn create_tag(&mut self, name: &str) -> Result<String, String> {
		handle_command(self.command().arg("tag").arg(name).arg("-f"))
	}

	fn list_branches(&mut self) -> Result<String, String> {
		handle_command(self.command().args(&["branches", "--color", "always"]))
	}

	fn create_branch(&mut self, name: &str) -> Result<String, String> {
		handle_command(self.command().arg("branch").arg(name))
	}

	fn close_branch(&mut self, name: &str) -> Result<String, String> {
		let changeset = handle_command(self.command().args(&["identify", "--num"]))?;
		self.update(name)?;

		let mut output = String::new();
		output.push_str(
			&handle_command(self.command().args(&[
				"commit",
				"-m",
				"\"close branch\"",
				"--close-branch",
			]))?[..],
		);
		output.push_str("\n");
		output.push_str(&self.update(changeset.trim())?[..]);

		Ok(output)
	}
}