From 9ca03713a2f84b9df0c8d9ad7c5e42b9d394de46 Mon Sep 17 00:00:00 2001 From: Kushashwa Ravi Shrimali Date: Sun, 25 Jun 2023 15:30:39 +0530 Subject: [PATCH 1/2] Add db - usage of db - avoid repetitive actions --- Cargo.toml | 2 + src/algo_loc.rs | 212 ++++++++++++++++++++++++-------------- src/config.rs | 2 + src/contextgpt_structs.rs | 3 +- src/db.rs | 70 +++++++++++++ src/main.rs | 25 ++++- 6 files changed, 234 insertions(+), 80 deletions(-) create mode 100644 src/db.rs diff --git a/Cargo.toml b/Cargo.toml index 50b3f01..7850e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ btree-map = "0.1.0" quicli = "0.4.0" structopt = "0.3.26" linecount = "0.1.0" +serde_json = "1.0.99" +serde = { version = "1.0.164", features = ["derive"] } diff --git a/src/algo_loc.rs b/src/algo_loc.rs index 7c977c9..0d3c4ec 100644 --- a/src/algo_loc.rs +++ b/src/algo_loc.rs @@ -1,9 +1,10 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::path::Path; use std::process::{Command, Stdio}; use crate::config::LAST_MANY_COMMIT_HASHES; use crate::contextgpt_structs::AuthorDetails; +use crate::db::DB; fn parse_str(input_str: &str, file_path: &str) -> Vec { let mut author_details_vec: Vec = vec![]; @@ -56,7 +57,7 @@ fn get_data_for_line( parsed_output: Vec, start_line_number: usize, end_line_number: usize, -) -> Vec { +) -> Option> { let mut output_list: Vec = vec![]; for output in parsed_output { if output.line_number >= start_line_number && output.line_number <= end_line_number { @@ -64,14 +65,38 @@ fn get_data_for_line( } } // TOOD: Address when line number is not valid or found - output_list + if output_list.is_empty() { + None + } else { + Some(output_list) + } } pub fn get_unique_files_changed( file_path: String, start_line_number: usize, end_line_number: usize, + db_obj: &mut DB, ) -> String { + let configured_file_path: String = + format!("{file_path}**{start_line_number}**{end_line_number}"); + // Check in the DB first + let mut res = String::new(); + let mut visited: HashMap = HashMap::new(); + if let Some(obj) = db_obj.exists(&configured_file_path) { + for author_detail in obj { + if visited.contains_key(&author_detail.file_path) { + continue; + } + visited.insert(author_detail.file_path.clone(), 1); + res.push_str(&author_detail.file_path); + res.push(','); + } + if res.ends_with(',') { + res.pop(); + } + return res; + } let mut binding = Command::new("git"); let command = binding.args([ "blame", @@ -91,7 +116,7 @@ pub fn get_unique_files_changed( get_data_for_line(parsed_output, start_line_number, end_line_number); let mut all_files_changed: Vec = Vec::new(); - for author_detail_for_line in vec_author_detail_for_line { + for author_detail_for_line in vec_author_detail_for_line.unwrap() { let val = author_detail_for_line; let mut commit_id = val.commit_hash; @@ -139,47 +164,59 @@ pub fn get_unique_files_changed( .unwrap(); let out_buf = String::from_utf8(new_blame_command.stdout).unwrap(); let parsed_buf = parse_str(out_buf.as_str(), &file_path); - let author_detail_for_line = - get_data_for_line(parsed_buf, val.line_number, val.line_number); - if author_detail_for_line.is_empty() { - break; - } - let val = author_detail_for_line.get(0).unwrap(); - commit_id = val.commit_hash.clone(); - let out_files_for_commit_hash = get_files_for_commit_hash(&commit_id); - for each_file in out_files_for_commit_hash { - let each_file_path = Path::new(&each_file); - if !each_file_path.exists() { - // NOTE: If file doesn't exist, maybe it was moved/renamed/deleted - so skip it for now - continue; + // let author_detail_for_line = + // get_data_for_line(parsed_buf, val.line_number, val.line_number); + // let val = author_detail_for_line.unwrap().get(0).unwrap(); + if let Some(valid_val) = get_data_for_line(parsed_buf, val.line_number, val.line_number) + { + commit_id = valid_val.get(0).unwrap().commit_hash.clone(); + let out_files_for_commit_hash = get_files_for_commit_hash(&commit_id); + for each_file in out_files_for_commit_hash { + let each_file_path = Path::new(&each_file); + if !each_file_path.exists() { + // NOTE: If file doesn't exist, maybe it was moved/renamed/deleted - so skip it for now + continue; + } + all_files_changed.push(each_file); + // let mut sanitized_file_path = each_file.clone(); + // // println!("Checking for {:?}", each_file); + // if !each_file_path.exists() { + // sanitized_file_path = get_correct_file_path(&each_file); + // // println!("Sanitized: {:?}", sanitized_file_path); + // // println!("Path before: {:?}", each_file); + // } + // all_files_changed.push(sanitized_file_path); } - all_files_changed.push(each_file); - // let mut sanitized_file_path = each_file.clone(); - // // println!("Checking for {:?}", each_file); - // if !each_file_path.exists() { - // sanitized_file_path = get_correct_file_path(&each_file); - // // println!("Sanitized: {:?}", sanitized_file_path); - // // println!("Path before: {:?}", each_file); - // } - // all_files_changed.push(sanitized_file_path); } } } - let sorted_map = all_files_changed - .iter() - .fold(BTreeMap::new(), |mut acc, c| { - *acc.entry(c.to_string()).or_insert(0) += 1; - acc - }); - let mut output_result = sorted_map.keys().fold(String::new(), |mut res, val| { - res.push_str(val); - res.push(','); - res - }); - if output_result.ends_with(',') { - output_result.pop(); + let mut res: HashMap = HashMap::new(); + for file_val in all_files_changed { + let details = AuthorDetails { + file_path: file_val.clone(), + ..Default::default() + }; + db_obj.append(&configured_file_path, details.clone()); + if res.contains_key(&file_val) { + let count = res.get(&file_val).unwrap() + 1; + res.insert(details.file_path, count); + continue; + } + res.insert(details.file_path, 0); + } + db_obj.store(); + let mut res_string: String = String::new(); + for key in res.keys() { + if key.contains("Commited Yet") { + continue; + } + res_string.push_str(key.as_str()); + res_string.push(','); } - output_result + if res_string.ends_with(',') { + res_string.pop(); + } + res_string } pub fn parse_follow(input_str: &str, input_path: &str) -> Option { @@ -252,15 +289,10 @@ pub fn _correct_file_path(path_obj: &Path) -> Option { "--first-parent", "--diff-filter=R", "--name-status", - // "|", - // "grep", - // path_obj.to_str().unwrap(), ]) .stdout(Stdio::piped()) .output() .unwrap(); - // println!("output: {:?}", output); - // println!("path: {:?}", path_obj.to_str().unwrap()); let stdout_buf = String::from_utf8(output.stdout).unwrap(); let parsed_output = parse_moved(stdout_buf.as_str(), path_obj.to_str().unwrap()); if let Some(final_path) = parsed_output { @@ -273,7 +305,30 @@ pub fn get_contextual_authors( file_path: String, start_line_number: usize, end_line_number: usize, + db_obj: &mut DB, ) -> String { + let configured_file_path: String = + format!("{file_path}**{start_line_number}**{end_line_number}"); + // Check in the DB first + let mut res = String::new(); + let mut visited: HashMap = HashMap::new(); + if let Some(obj) = db_obj.exists(&configured_file_path) { + for author_detail in obj { + if visited.contains_key(&author_detail.author_full_name) { + continue; + } + if author_detail.author_full_name.contains("Not Committed Yet") { + continue; + } + visited.insert(author_detail.author_full_name.clone(), 1); + res.push_str(&author_detail.author_full_name); + res.push(','); + } + if res.ends_with(',') { + res.pop(); + } + return res; + } let output = Command::new("git") .args([ "blame", @@ -292,21 +347,19 @@ pub fn get_contextual_authors( let parsed_output = parse_str(stdout_buf.as_str(), &file_path); let vec_author_detail_for_line = - get_data_for_line(parsed_output, start_line_number, end_line_number); - // TODO: Use this function when files don't exist and have been moved/renamed - // vec_author_detail_for_line = fix_details_in_case_of_move(vec_author_detail_for_line.clone()); + get_data_for_line(parsed_output, start_line_number, end_line_number).unwrap_or(Vec::new()); - let mut author_details: Vec = Vec::new(); + let mut author_details: Vec = Vec::new(); for author_detail_for_line in vec_author_detail_for_line { - let val = author_detail_for_line; - author_details.push(val.author_full_name); + author_details.push(author_detail_for_line.clone()); - let mut commit_id = val.commit_hash; + let mut commit_id = author_detail_for_line.clone().commit_hash; let mut blame_count: i32 = 0; while blame_count != LAST_MANY_COMMIT_HASHES { blame_count += 1; - let line_string: String = - val.line_number.to_string() + &','.to_string() + &val.line_number.to_string(); + let line_string: String = author_detail_for_line.line_number.to_string() + + &','.to_string() + + &author_detail_for_line.line_number.to_string(); let commit_url = commit_id.clone() + "^"; let cmd_args = vec![ "blame", @@ -325,31 +378,36 @@ pub fn get_contextual_authors( .unwrap(); let out_buf = String::from_utf8(new_blame_command.stdout).unwrap(); let parsed_buf = parse_str(out_buf.as_str(), &file_path); - let author_detail_for_line = - get_data_for_line(parsed_buf, val.line_number, val.line_number); - if author_detail_for_line.is_empty() { - break; + + if let Some(valid_val) = get_data_for_line( + parsed_buf, + author_detail_for_line.line_number, + author_detail_for_line.line_number, + ) { + commit_id = valid_val.get(0).unwrap().commit_hash.clone(); + author_details.push(author_detail_for_line.clone()); } - let val = author_detail_for_line.get(0).unwrap(); - commit_id = val.commit_hash.clone(); - author_details.push(val.author_full_name.clone()); } } - let sorted_map = author_details.iter().fold(BTreeMap::new(), |mut acc, c| { - *acc.entry(c.to_string()).or_insert(0) += 1; - acc - }); - let reverse_sorted_map: BTreeMap<&i32, &String> = - sorted_map.iter().map(|(k, v)| (v, k)).collect(); - let mut res = reverse_sorted_map - .values() - .fold(String::new(), |mut res, val| { - res.push_str(val); - res.push(','); - res - }); - if res.ends_with(',') { - res.pop(); + + let mut res: HashMap = HashMap::new(); + for author_detail_val in author_details { + db_obj.append(&configured_file_path, author_detail_val.clone()); + if res.contains_key(&author_detail_val.author_full_name) { + let count = res.get(&author_detail_val.author_full_name).unwrap() + 1; + res.insert(author_detail_val.author_full_name, count); + continue; + } + res.insert(author_detail_val.author_full_name, 0); + } + db_obj.store(); + let mut res_string: String = String::new(); + for key in res.keys() { + if key.contains("Not Committed Yet") { + continue; + } + res_string.push_str(key.as_str()); + res_string.push(','); } - res + res_string } diff --git a/src/config.rs b/src/config.rs index 3570341..2e00577 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1 +1,3 @@ pub const LAST_MANY_COMMIT_HASHES: i32 = 5; +pub const AUTHOR_DB_PATH: &str = "db_author.json"; +pub const FILE_DB_PATH: &str = "db_file.json"; diff --git a/src/contextgpt_structs.rs b/src/contextgpt_structs.rs index c68e100..fe54ae2 100644 --- a/src/contextgpt_structs.rs +++ b/src/contextgpt_structs.rs @@ -1,4 +1,5 @@ use structopt::StructOpt; +use serde::{Deserialize, Serialize}; #[derive(Debug, StructOpt)] pub struct Cli { @@ -13,7 +14,7 @@ pub struct Cli { pub request_type: String, } -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct AuthorDetails { pub commit_hash: String, pub author_full_name: String, diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..67eddfc --- /dev/null +++ b/src/db.rs @@ -0,0 +1,70 @@ +use std::{ + collections::HashMap, + fs::{write, File}, + io::{Read, Write}, + path::Path, +}; + +use crate::{config, contextgpt_structs::AuthorDetails}; + +#[derive(Default)] +pub struct DB { + pub db_file_path: String, + pub current_data: HashMap>, +} + +impl DB { + pub fn read(&mut self, file_obj: &mut File) -> HashMap> { + let data_buffer = std::fs::read_to_string(self.db_file_path.clone()).unwrap(); + let v: HashMap> = serde_json::from_str(&data_buffer.as_str()) + .expect("Unable to deserialize the file, something went wrong"); + v + } + + pub fn init_db(&mut self) { + let db_path_obj: &Path = Path::new(&self.db_file_path); + if !db_path_obj.exists() { + File::create(db_path_obj).expect("Couldn't create the file for some reason"); + self.current_data = HashMap::new(); + return; + } + let mut file_obj = + File::open(self.db_file_path.as_str()).expect("Couldn't open the given file"); + self.current_data = self.read(&mut file_obj); + } + + pub fn append(&mut self, configured_file_path: &String, data: AuthorDetails) { + let mut existing_data = vec![]; + if self.current_data.contains_key(configured_file_path) { + existing_data = self + .current_data + .get_mut(configured_file_path) + .unwrap() + .to_vec(); + existing_data.append(&mut vec![data]); + } else { + existing_data.append(&mut vec![data]); + } + self.current_data + .insert(configured_file_path.to_string(), existing_data); + } + + pub fn store(&mut self) { + let mut file_obj = + File::create(self.db_file_path.as_str()).expect("Couldn't open the given file"); + let output_string = + serde_json::to_string_pretty(&self.current_data).expect("Unable to write data"); + // file_obj + // .write_all(output_string.as_bytes()) + // .expect("Unable to write bytes to the file"); + write!(file_obj, "{}", output_string).expect("Couldn't write, uhmmm"); + } + + pub fn exists(&self, search_field: &String) -> Option<&Vec> { + if self.current_data.contains_key(search_field) { + self.current_data.get(search_field) + } else { + None + } + } +} diff --git a/src/main.rs b/src/main.rs index 0dfb272..86415e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod algo_loc; mod authordetails_impl; mod config; mod contextgpt_structs; +mod db; use linecount::count_lines; use quicli::prelude::*; @@ -29,11 +30,31 @@ fn main() -> CliResult { } else { end_line_number }; + let mut auth_db_obj = db::DB { + db_file_path: config::AUTHOR_DB_PATH.to_string(), + ..Default::default() + }; + let mut file_db_obj = db::DB { + db_file_path: config::FILE_DB_PATH.to_string(), + ..Default::default() + }; if args.request_type.starts_with("aut") { - let output = get_contextual_authors(args.file, args.start_number, valid_end_line_number); + auth_db_obj.init_db(); + let output = get_contextual_authors( + args.file, + args.start_number, + valid_end_line_number, + &mut auth_db_obj, + ); println!("{:?}", output); } else { - let output = get_unique_files_changed(args.file, args.start_number, valid_end_line_number); + file_db_obj.init_db(); + let output = get_unique_files_changed( + args.file, + args.start_number, + valid_end_line_number, + &mut file_db_obj, + ); println!("{:?}", output); } Ok(()) From 494e2ad5d4ec8d6baa1a6201c134da0d7405ae96 Mon Sep 17 00:00:00 2001 From: Kushashwa Ravi Shrimali Date: Sun, 25 Jun 2023 15:50:20 +0530 Subject: [PATCH 2/2] Save in the home folder - checking simple-home-dir crate for cross platform support --- Cargo.toml | 1 + src/config.rs | 1 + src/db.rs | 13 ++++++++++++- src/main.rs | 4 ++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7850e86..e3a157d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ structopt = "0.3.26" linecount = "0.1.0" serde_json = "1.0.99" serde = { version = "1.0.164", features = ["derive"] } +simple-home-dir = "0.1.2" diff --git a/src/config.rs b/src/config.rs index 2e00577..7a2ab62 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ pub const LAST_MANY_COMMIT_HASHES: i32 = 5; pub const AUTHOR_DB_PATH: &str = "db_author.json"; pub const FILE_DB_PATH: &str = "db_file.json"; +pub const DB_FOLDER: &str = ".context_pilot_db"; diff --git a/src/db.rs b/src/db.rs index 67eddfc..6182812 100644 --- a/src/db.rs +++ b/src/db.rs @@ -9,8 +9,9 @@ use crate::{config, contextgpt_structs::AuthorDetails}; #[derive(Default)] pub struct DB { - pub db_file_path: String, + pub db_file_name: String, pub current_data: HashMap>, + pub db_file_path: String, } impl DB { @@ -22,6 +23,16 @@ impl DB { } pub fn init_db(&mut self) { + let folder_path = Path::new(simple_home_dir::home_dir().unwrap().to_str().unwrap()) + .join(config::DB_FOLDER); + self.db_file_path = folder_path + .join(&self.db_file_name) + .to_str() + .unwrap() + .to_string(); + // Create folder + std::fs::create_dir_all(folder_path) + .expect("unable to create folder, something went wrong"); let db_path_obj: &Path = Path::new(&self.db_file_path); if !db_path_obj.exists() { File::create(db_path_obj).expect("Couldn't create the file for some reason"); diff --git a/src/main.rs b/src/main.rs index 86415e5..6c38b6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,11 +31,11 @@ fn main() -> CliResult { end_line_number }; let mut auth_db_obj = db::DB { - db_file_path: config::AUTHOR_DB_PATH.to_string(), + db_file_name: config::AUTHOR_DB_PATH.to_string(), ..Default::default() }; let mut file_db_obj = db::DB { - db_file_path: config::FILE_DB_PATH.to_string(), + db_file_name: config::FILE_DB_PATH.to_string(), ..Default::default() }; if args.request_type.starts_with("aut") {