diff options
author | Tim Oram <mitmaro@gmail.com> | 2017-03-27 00:02:50 -0230 |
---|---|---|
committer | Tim Oram <mitmaro@gmail.com> | 2017-03-27 09:27:21 -0230 |
commit | da2fbbcb51a37c19c0d2d09e0fa47991f1cff141 (patch) | |
tree | 0e3beff70d0e06ed108c89560b238d1bd6b73179 /src | |
parent | 5434c39a72e7d4d0e75187c4fd729912a72590d9 (diff) |
Added show commit info
closes #25
Diffstat (limited to 'src')
-rw-r--r-- | src/application.rs | 40 | ||||
-rw-r--r-- | src/commit.rs | 97 | ||||
-rw-r--r-- | src/git_interactive.rs | 19 | ||||
-rw-r--r-- | src/main.rs | 15 | ||||
-rw-r--r-- | src/window.rs | 92 |
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)); |