diff --git a/.gitignore b/.gitignore index 2f7896d..cbe34e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target/ +something/ diff --git a/Cargo.lock b/Cargo.lock index 651c002..6fc948e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,425 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06badb543e734a2d6568e19a40af66ed5364360b9226184926f89d229b4b4267" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "strsim", + "termcolor", + "unicase", + "unicode-width", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "unicode-width", + "winapi", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "gitignore" version = "0.1.0" +dependencies = [ + "clap", + "console", + "eyre", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +dependencies = [ + "itoa", + "libc", + "num_threads", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7232611 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Git ignore + +Is an extension for easily adding ignored files to `.gitignore files`, when +added it will by default also try to remove any files matching the pattern added +to .gitignore, this is by default also run in interactive mode, giving you the +option to confirm or deny + +```bash +$ git ignore 'node_modules/' +Added node_modules/ to .gitignore +Searching env for pattern... + +found: +/client/node_modules +? Remove from git state? (Y)es/(N)o/(C)ontinue/(A)bort +``` diff --git a/crates/gitignore/.gitignore b/crates/gitignore/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/crates/gitignore/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/crates/gitignore/Cargo.toml b/crates/gitignore/Cargo.toml index 56886ba..d0dc34b 100644 --- a/crates/gitignore/Cargo.toml +++ b/crates/gitignore/Cargo.toml @@ -7,4 +7,7 @@ edition = "2021" [dependencies] clap = { version = "4.0.17", features = ["env", "unicode", "string"] } +console = "0.15.2" eyre = "0.6.8" +tracing = { version = "0.1.37", features = ["log"] } +tracing-subscriber = { version = "0.3.16", features = ["local-time", "env-filter"] } diff --git a/crates/gitignore/src/.main.rs.rustfmt b/crates/gitignore/src/.main.rs.rustfmt new file mode 100644 index 0000000..2778e75 --- /dev/null +++ b/crates/gitignore/src/.main.rs.rustfmt @@ -0,0 +1,191 @@ +use clap::{Arg, Command}; +use eyre::{Context, ContextCompat}; +use std::io::prelude::*; +use std::{env::current_dir, io::Read, path::PathBuf}; + +fn main() -> eyre::Result<()> { + let matches = Command::new("gitignore") + .version("0.1") + .author("Kasper J. Hermansen ") + .about("Easily ignore items and remove from git state") + .long_about("git ignore is a utility tool for easily adding patterns to your .gitignore file. +Easily add patterns using `git ignore ` this will by default also help you remove committed code violating these patterns + ") + .propagate_version(true) + .arg( + Arg::new("pattern") + .help("the pattern you want to ignore") + .long_help("the pattern you want to ignore in the nearest .gitignore file") + .required(true), + ).arg( + Arg::new("log-level").long("log-level").help("choose a log level and get more messages").long_help("Choose a log level and get more message, defaults to [INFO]")) + .get_matches(); + + let pattern = matches + .get_one::("pattern") + .context("missing [pattern]")?; + + add_gitignore_pattern(pattern) +} + +enum GitActions { + AddPattern { + git_path: PathBuf, + gitignore_path: PathBuf, + }, + CreateIgnoreAndAddPattern { + git_path: PathBuf, + }, +} + +fn add_gitignore_pattern(pattern: &String) -> eyre::Result<()> { + let curdir = current_dir().context( + "could not find current_dir, you may not have permission to access that directory", + )?; + let actions = match search_for_dotgitignore(&curdir)? { + // If we have an ignore path, make sure it is in a git repo as well + GitSearchResult::GitIgnore(ignorepath) => match search_for_git_root(&curdir)? { + GitSearchResult::Git(gitpath) => GitActions::AddPattern { + git_path: gitpath, + gitignore_path: ignorepath, + }, + _ => return Err(eyre::anyhow!("could not find parent git directory")), + }, + // Find the nearest git repo + GitSearchResult::Git(gitpath) => { + GitActions::CreateIgnoreAndAddPattern { git_path: gitpath } + } // We will always have either above, or an error so we have no default arm + }; + + match actions { + GitActions::AddPattern { + git_path, + gitignore_path, + } => { + let mut gitignore_file = open_gitignore_file(&gitignore_path)?; + // TODO: search for pattern in file + let mut gitignore_content = String::new(); + gitignore_file + .read_to_string(&mut gitignore_content) + .context(format!( + "could not read file: {}", + gitignore_path.to_string_lossy() + ))?; + if gitignore_content.contains(pattern) { + return Ok(()); + } + + writeln!(gitignore_file, "{}", pattern).context("could not write contents to file")?; + gitignore_file + .sync_all() + .context("failed to write data to disk")?; + } + GitActions::CreateIgnoreAndAddPattern { git_path } => { + // TODO: Create gitignore file in root + let mut gitignore_file = create_gitignore_file(&git_path)?; + // TODO: do same as above + writeln!(gitignore_file, "{}", pattern).context("could not write contents to file")?; + gitignore_file + .sync_all() + .context("failed to write data to disk")?; + } + } + + // TODO: Run git rm -r --cached on the .git root + + Ok(()) +} + +fn create_gitignore_file(gitroot: &PathBuf) -> eyre::Result { + let mut ignore_path = gitroot.clone(); + if !ignore_path.pop() { + return Err(eyre::anyhow!("could not open parent dir")); + } + ignore_path.push(".gitignore"); + let file = std::fs::File::create(ignore_path.clone()).context(format!( + "could not create file at path: {}", + ignore_path.to_string_lossy() + ))?; + + Ok(file) +} + +fn open_gitignore_file(gitignore: &PathBuf) -> eyre::Result { + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(gitignore) + .context(format!( + "could not create file at path: {}", + gitignore.to_string_lossy() + ))?; + + return Ok(file); +} + +enum GitSearchResult { + GitIgnore(PathBuf), + Git(PathBuf), +} + +fn search_for_git_root(path: &PathBuf) -> eyre::Result { + if !path.is_dir() { + return Err(eyre::anyhow!( + "path is not a dir: {}", + path.to_string_lossy() + )); + } + + let direntries = std::fs::read_dir(path) + .context(format!("could not open dir: {}", path.to_string_lossy()))?; + for direntry in direntries { + let entry = direntry.context("could not access file")?; + + let file_name = entry.file_name().to_os_string(); + match file_name.to_str().context("could not convert to str")? { + ".git" => return Ok(GitSearchResult::Git(entry.path())), + _ => {} + } + } + + let mut upwards_par = path.clone(); + if !upwards_par.pop() { + return Err(eyre::anyhow!( + "no parent exists, cannot check further, you may not be in a git repository" + )); + } + + search_for_git_root(&upwards_par) +} + +fn search_for_dotgitignore(path: &PathBuf) -> eyre::Result { + if !path.is_dir() { + return Err(eyre::anyhow!( + "path is not a dir: {}", + path.to_string_lossy() + )); + } + + let direntries = std::fs::read_dir(path) + .context(format!("could not open dir: {}", path.to_string_lossy()))?; + for direntry in direntries { + let entry = direntry.context("could not access file")?; + + let file_name = entry.file_name().to_os_string(); + + match file_name.to_str().context("could not convert to str")? { + ".gitignore" => return Ok(GitSearchResult::GitIgnore(entry.path())), + ".git" => return Ok(GitSearchResult::Git(entry.path())), + _ => {} + } + } + + let mut upwards_par = path.clone(); + if !upwards_par.pop() { + return Err(eyre::anyhow!( + "no parent exists, cannot check further, you may not be in a git repository" + )); + } + + search_for_dotgitignore(&upwards_par) +} diff --git a/crates/gitignore/src/main.rs b/crates/gitignore/src/main.rs index 9581027..9289cc2 100644 --- a/crates/gitignore/src/main.rs +++ b/crates/gitignore/src/main.rs @@ -2,6 +2,8 @@ use clap::{Arg, Command}; use eyre::{Context, ContextCompat}; use std::io::prelude::*; use std::{env::current_dir, io::Read, path::PathBuf}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; fn main() -> eyre::Result<()> { let matches = Command::new("gitignore") @@ -17,14 +19,34 @@ Easily add patterns using `git ignore ` this will by default also help .help("the pattern you want to ignore") .long_help("the pattern you want to ignore in the nearest .gitignore file") .required(true), - ) + ).arg( + Arg::new("log-level").long("log-level").help("choose a log level and get more messages").long_help("Choose a log level and get more message, defaults to [fatal]")) .get_matches(); + let log_level = match matches.get_one::("log-level").map(|f| f.as_str()) { + Some("off") => "off", + Some("info") => "info", + Some("debug") => "debug", + Some("warn") => "warn", + Some("error") => "error", + _ => "error", + }; + + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new(format!( + "gitignore={}", + log_level + ))) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let term = console::Term::stdout(); + let pattern = matches .get_one::("pattern") .context("missing [pattern]")?; - add_gitignore_pattern(pattern) + add_gitignore_pattern(term, pattern) } enum GitActions { @@ -37,7 +59,8 @@ enum GitActions { }, } -fn add_gitignore_pattern(pattern: &String) -> eyre::Result<()> { +fn add_gitignore_pattern(term: console::Term, pattern: &String) -> eyre::Result<()> { + term.write_line("git ignore: Add pattern")?; let curdir = current_dir().context( "could not find current_dir, you may not have permission to access that directory", )?; @@ -61,8 +84,8 @@ fn add_gitignore_pattern(pattern: &String) -> eyre::Result<()> { git_path, gitignore_path, } => { + term.write_line("Found existing .gitignore")?; let mut gitignore_file = open_gitignore_file(&gitignore_path)?; - // TODO: search for pattern in file let mut gitignore_content = String::new(); gitignore_file .read_to_string(&mut gitignore_content) @@ -71,18 +94,22 @@ fn add_gitignore_pattern(pattern: &String) -> eyre::Result<()> { gitignore_path.to_string_lossy() ))?; if gitignore_content.contains(pattern) { + term.write_line(".gitignore already contains pattern, skipping")?; return Ok(()); } + term.write_line("adding pattern to file")?; writeln!(gitignore_file, "{}", pattern).context("could not write contents to file")?; gitignore_file .sync_all() .context("failed to write data to disk")?; } GitActions::CreateIgnoreAndAddPattern { git_path } => { - // TODO: Create gitignore file in root + term.write_line( + "could not find .gitignore file, creating one in the root of the git repository", + )?; let mut gitignore_file = create_gitignore_file(&git_path)?; - // TODO: do same as above + term.write_line("adding pattern to file")?; writeln!(gitignore_file, "{}", pattern).context("could not write contents to file")?; gitignore_file .sync_all()