summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTim Oram <mitmaro@gmail.com>2017-03-27 00:02:50 -0230
committerTim Oram <mitmaro@gmail.com>2017-03-27 09:27:21 -0230
commitda2fbbcb51a37c19c0d2d09e0fa47991f1cff141 (patch)
tree0e3beff70d0e06ed108c89560b238d1bd6b73179 /src
parent5434c39a72e7d4d0e75187c4fd729912a72590d9 (diff)
Added show commit info
closes #25
Diffstat (limited to 'src')
-rw-r--r--src/application.rs40
-rw-r--r--src/commit.rs97
-rw-r--r--src/git_interactive.rs19
-rw-r--r--src/main.rs15
-rw-r--r--src/window.rs92
5 files changed, 250 insertions, 13 deletions
diff --git a/src/application.rs b/src/application.rs
index ad61617..0bc40de 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -9,8 +9,10 @@ use window::{
const EXIT_CODE_GOOD: i32 = 0;
const EXIT_CODE_WRITE_ERROR: i32 = 8;
+#[derive(PartialEq, Debug)]
pub enum State {
List,
+ ShowCommit,
Help
}
@@ -34,6 +36,7 @@ impl Application {
pub fn process_input(&mut self) {
match self.state {
State::List => self.process_list_input(),
+ State::ShowCommit => self.process_show_commit_input(),
State::Help => self.process_help_input()
}
}
@@ -46,6 +49,12 @@ impl Application {
self.git_interactive.get_selected_line_index()
);
},
+ State::ShowCommit => {
+ self.window.draw_show_commit(
+ self.git_interactive.get_selected_line_hash(),
+ self.git_interactive.get_git_root()
+ );
+ },
State::Help => {
self.window.draw_help();
}
@@ -78,11 +87,19 @@ impl Application {
self.state = State::List;
}
+ fn process_show_commit_input(&mut self) {
+ self.window.window.getch();
+ self.state = State::List;
+ }
+
fn process_list_input(&mut self) {
match self.window.window.getch() {
Some(Input::Character(c)) if c == '?' => {
self.state = State::Help;
},
+ Some(Input::Character(c)) if c == 'c' => {
+ self.state = State::ShowCommit;
+ },
Some(Input::Character(c))
if (c == 'Q') || (c == 'q' && self.window.confirm("Are you sure you want to abort"))
=> self.abort(),
@@ -139,11 +156,11 @@ impl Application {
}
}
-
#[cfg(test)]
mod tests {
use super::{
Application,
+ State
};
use git_interactive::GitInteractive;
use window::{
@@ -161,6 +178,27 @@ mod tests {
}
#[test]
+ fn application_show_help() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('?');
+ app.process_input();
+ assert_eq!(app.state, State::Help);
+ }
+
+ #[test]
+ fn application_show_commit() {
+ // first commit in
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-show-commit.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('c');
+ app.process_input();
+ assert_eq!(app.state, State::ShowCommit);
+ }
+
+ #[test]
fn application_scroll_basic() {
let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-long.in").unwrap();
let window = Window::new();
diff --git a/src/commit.rs b/src/commit.rs
new file mode 100644
index 0000000..5b91573
--- /dev/null
+++ b/src/commit.rs
@@ -0,0 +1,97 @@
+#[derive(PartialEq, Debug)]
+pub struct FileStat {
+ name: String,
+ added: String,
+ removed: String
+}
+
+#[derive(PartialEq, Debug)]
+pub struct Commit {
+ author_name: String,
+ author_email: String,
+ date: String,
+ subject: String,
+ body: String,
+ file_stats: Vec<FileStat>
+}
+
+impl FileStat {
+ pub fn new(file_stats: &str) -> Result<Self, String> {
+ let input: Vec<&str> = file_stats.splitn(3, '\t').collect();
+
+ match input.len() {
+ 3 => Ok(FileStat {
+ name: String::from(input[2]),
+ added: String::from(input[0]),
+ removed: String::from(input[1])
+ }),
+ _ => Err(format!(
+ "Invalid file stat result:\n{}", file_stats
+ ))
+ }
+ }
+
+ pub fn get_added(&self) -> &String {
+ &self.added
+ }
+
+ pub fn get_removed(&self) -> &String {
+ &self.removed
+ }
+
+ pub fn get_name(&self) -> &String {
+ &self.name
+ }
+}
+
+impl Commit {
+ pub fn new(commit_stat: &str) -> Result<Self, String> {
+ let input: Vec<&str> = commit_stat.splitn(6, '').collect();
+ match input.len() {
+ 6 => {
+ let file_stats = input[5].lines()
+ .filter(|l| !l.is_empty())
+ .map(|l| FileStat::new(l))
+ .collect();
+ match file_stats {
+ Ok(stats) => Ok(Commit {
+ author_name: String::from(input[0]),
+ author_email: String::from(input[1]),
+ date: String::from(input[2]),
+ subject: String::from(input[3]),
+ body: String::from(input[4]),
+ file_stats: stats
+ }),
+ Err(e) => Err(e)
+ }
+ },
+ _ => Err(format!(
+ "Invalid stat result:\n{}\n{}", commit_stat, input.len()
+ ))
+ }
+ }
+
+ pub fn get_author_name(&self) -> &String {
+ &self.author_name
+ }
+
+ pub fn get_author_email(&self) -> &String {
+ &self.author_email
+ }
+
+ pub fn get_date(&self) -> &String {
+ &self.date
+ }
+
+ pub fn get_subject(&self) -> &String {
+ &self.subject
+ }
+
+ pub fn get_body(&self) -> &String {
+ &self.body
+ }
+
+ pub fn get_file_stats(&self) -> &Vec<FileStat> {
+ &self.file_stats
+ }
+}
diff --git a/src/git_interactive.rs b/src/git_interactive.rs
index 65ae357..c4c3678 100644
--- a/src/git_interactive.rs
+++ b/src/git_interactive.rs
@@ -9,6 +9,7 @@ use action::Action;
use line::Line;
pub struct GitInteractive {
+ git_root: PathBuf,
filepath: PathBuf,
lines: Vec<Line>,
selected_line_index: usize
@@ -17,7 +18,7 @@ pub struct GitInteractive {
impl GitInteractive {
pub fn new_from_filepath(filepath: &str) -> Result<Self, String> {
let path = PathBuf::from(filepath);
-
+
let mut file = match File::open(&path) {
Ok(file) => file,
Err(why) => {
@@ -27,7 +28,7 @@ impl GitInteractive {
));
}
};
-
+
let mut s = String::new();
match file.read_to_string(&mut s) {
Ok(_) => {},
@@ -38,7 +39,10 @@ impl GitInteractive {
));
}
}
-
+
+ let mut git_root = PathBuf::from(filepath);
+ git_root.pop();
+
// catch noop rebases
let parsed_result = match s.lines().nth(0) {
Some("noop") => Ok(Vec::new()),
@@ -53,6 +57,7 @@ impl GitInteractive {
match parsed_result {
Ok(lines) => Ok(
GitInteractive {
+ git_root: git_root,
filepath: path,
lines: lines,
selected_line_index: 1
@@ -124,10 +129,18 @@ impl GitInteractive {
self.lines[self.selected_line_index - 1].set_action(action);
}
+ pub fn get_selected_line_hash(&self) -> &String {
+ self.lines[self.selected_line_index - 1].get_hash()
+ }
+
pub fn get_selected_line_index(&self) -> &usize {
&self.selected_line_index
}
+ pub fn get_git_root(&self) -> &PathBuf {
+ &self.git_root
+ }
+
pub fn get_lines(&self) -> &Vec<Line> {
&self.lines
}
diff --git a/src/main.rs b/src/main.rs
index b237ac8..5a21829 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,12 @@
// TODO:
// - Add execute command
extern crate pancurses;
+extern crate pad;
use std::env;
use std::process;
+mod commit;
mod action;
mod application;
mod git_interactive;
@@ -20,6 +22,7 @@ use git_interactive::GitInteractive;
use window::Window;
fn main() {
+
let filepath = match env::args().nth(1) {
Some(filepath) => filepath,
None => {
@@ -30,7 +33,7 @@ fn main() {
process::exit(1);
}
};
-
+
let git_interactive = match GitInteractive::new_from_filepath(&filepath) {
Ok(gi) => gi,
Err(msg) => {
@@ -38,21 +41,21 @@ fn main() {
process::exit(1);
}
};
-
+
if git_interactive.get_lines().is_empty() {
print_err!("{}", &"Nothing to rebase");
process::exit(0);
}
-
+
let window = Window::new();
-
+
let mut application = Application::new(git_interactive, window);
-
+
while application.exit_code == None {
application.draw();
application.process_input()
}
-
+
match application.end() {
Ok(_) => {},
Err(msg) => {
diff --git a/src/window.rs b/src/window.rs
index f4d1649..c7ad577 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -1,3 +1,10 @@
+use std::cmp;
+use std::path::PathBuf;
+use std::process::{
+ Command
+};
+use std::error::Error;
+use pad::{PadStr, Alignment};
#[cfg(not(test))]
use pancurses as pancurses;
@@ -13,6 +20,8 @@ use action::{
};
use line::Line;
+use commit::Commit;
+
const COLOR_TABLE: [i16; 7] = [
pancurses::COLOR_WHITE,
pancurses::COLOR_YELLOW,
@@ -135,11 +144,87 @@ impl Window {
self.window.mvaddstr(
self.window.get_max_y() - 1,
0,
- "Actions: [ up, down, q/Q, w/W, j, k, p, r, e, s, f, d, ? ]"
+ "Actions: [ up, down, q/Q, w/W, c, j, k, p, r, e, s, f, d, ? ]"
);
self.set_dim(false);
}
-
+
+ pub fn draw_show_commit(&self, commit: &str, git_root: &PathBuf) {
+ let result = Command::new("git")
+ .current_dir(git_root)
+ .args(&[
+ "diff-tree",
+ "--numstat",
+ "--format=%aN%x1E%aE%x1E%ad%x1E%s%x1E%b%x1E",
+ commit
+ ])
+ .output()
+ ;
+
+ self.window.clear();
+ self.draw_title();
+ match result {
+ Ok(output) => {
+ self.set_color(Color::White);
+ match Commit::new(&String::from_utf8_lossy(&output.stdout)) {
+ Ok(commit_data) => {
+ self.set_color(Color::Yellow);
+ self.window.addstr(&format!("\nCommit: {}\n", commit));
+ self.set_color(Color::White);
+ self.window.addstr(&format!(
+ "Author: {} <{}>\n", commit_data.get_author_name(), commit_data.get_author_email()
+ ));
+ self.window.addstr(&format!(
+ "Date: {}\n",
+ commit_data.get_date()
+ ));
+
+ self.window.addstr(&format!(
+ "\n{}\n\n{}\n",
+ commit_data.get_subject(),
+ commit_data.get_body()
+ ));
+ let max_add_change_length = commit_data
+ .get_file_stats()
+ .iter()
+ .fold(0, |a, x| cmp::max(a, x.get_added().len()));
+
+ let max_remove_change_length = commit_data
+ .get_file_stats()
+ .iter()
+ .fold(0, |a, x| cmp::max(a, x.get_added().len()));
+
+ for file_stat in commit_data.get_file_stats() {
+ self.set_color(Color::Green);
+ self.window.addstr(
+ &file_stat.get_added().pad_to_width_with_alignment(max_add_change_length, Alignment::Right)
+ );
+ self.set_color(Color::White);
+ self.window.addstr(" | ");
+ self.set_color(Color::Red);
+ self.window.addstr(
+ &file_stat.get_removed().pad_to_width_with_alignment(max_remove_change_length, Alignment::Left)
+ );
+ self.set_color(Color::White);
+ self.window.addstr(&format!(" {}\n", &file_stat.get_name()));
+ }
+ },
+ Err(msg) => {
+ self.set_color(Color::Red);
+ self.window.addstr(&msg);
+ }
+ }
+ },
+ Err(msg) => {
+ self.set_color(Color::Red);
+ self.window.addstr(msg.description());
+ }
+ }
+ self.set_color(Color::Yellow);
+ self.window.addstr("\n\nHit any key to close");
+ self.window.refresh();
+ }
+
pub fn draw_help(&self) {
self.window.clear();
self.draw_title();
@@ -155,6 +240,7 @@ impl Window {
self.draw_help_command("w", "Write interactive rebase file");
self.draw_help_command("W", "Immediately write interactive rebase file");
self.draw_help_command("?", "Show help");
+ self.draw_help_command("c", "Show commit information");
self.draw_help_command("j", "Move selected commit down");
self.draw_help_command("k", "Move selected commit up");
self.draw_help_command("p", "Set selected commit to be picked");
@@ -166,7 +252,7 @@ impl Window {
self.window.addstr("\n\nHit any key to close help");
self.window.refresh();
}
-
+
fn draw_help_command(&self, command: &str, help: &str) {
self.set_color(Color::Blue);
self.window.addstr(&format!(" {:9} ", command));