From 4a012b28358ac157da5974488ea27d93025c2852 Mon Sep 17 00:00:00 2001 From: John Backes Date: Mon, 17 Apr 2023 17:42:36 -0700 Subject: [PATCH 1/4] Add git-commit-gpt installation script Body: This commit adds a Bash script that installs git-commit-gpt, a tool that integrates OpenAI's GPT-3 language model into Git commit messages to help write more descriptive and professional messages. The script checks if happycommit is in the user's PATH before proceeding with the installation. It also adds the git-commit-gpt command to the user's shell configuration file to ensure it's available every time the shell starts up. If the OPENAI_API_KEY is not in the happycommit config file, the script prompts the user to enter it and adds it to the file. If it's already in the file, the script gives the user the option to update it. The script is officially supported on Bash and Zsh, and works on macOS as well as other Unix-based systems. Testing: The installation script was successfully tested on a macOS machine running zsh. It has not been tested on a Linux machine. and on an Ubuntu machine running Zsh. The script successfully detected an invalid happycommit installation and prompted the user to install it. The script also successfully added git-commit-gpt to the .zshrc file on the Mac. Additionally, the script prompted the user to enter the OPENAI_API_KEY and added it to the config.toml file. --- install-commit-gpt.sh | 102 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100755 install-commit-gpt.sh diff --git a/install-commit-gpt.sh b/install-commit-gpt.sh new file mode 100755 index 0000000..47163ac --- /dev/null +++ b/install-commit-gpt.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# The user should always run as "source" and not execute directly +if [ "$0" = "$BASH_SOURCE" ]; then + printf "Please run this script as 'source install-commit-gpt.sh'\n" + exit 1 +fi + +# Check if happycommit is in the user's PATH +if ! command -v happycommit &> /dev/null; then + printf "happycommit not found in your PATH. Please install it first.\n" + exit 1 +fi + +current_shell=$(basename "$SHELL") + +# Add the git commit-gpt command to the user's shell configuration file +config_file="" +case "$current_shell" in + bash) + config_file="$HOME/.bashrc" + ;; + zsh) + config_file="$HOME/.zshrc" + ;; + *) + printf "Unsupported shell. Please add manually.\n" + exit 1 + ;; +esac + +# Check if it's Darwin so sed works properly +unameOut="$(uname -s)" +case "${unameOut}" in + Darwin*) machine=Mac;; + *) machine="UNKNOWN:${unameOut}" +esac + +printf 'Installing git command and adding to your "$PATH"\n' +# Create the directory ~/.happycommit/bin and copy the happycommit binary to it +mkdir -p ~/.happycommit/bin +cp git-commit-gpt ~/.happycommit/bin +chmod +x ~/.happycommit/bin/git-commit-gpt + +# Add the directory to the user's PATH +# if path is already in the PATH, don't add it again +if ! echo "$PATH" | grep -q "$HOME/.happycommit/bin"; then + printf "export PATH=\"$HOME/.happycommit/bin:\$PATH\"\n" >> "$config_file" +fi + +# Reload the shell configuration file +source "$config_file" + +# make sure git commit-gpt exists +if ! command -v git-commit-gpt &> /dev/null; then + printf "git commit-gpt not found in your PATH. Please add it manually.\n" + exit 1 +fi + +# Ask for user's OPENAI_API_KEY so it can be added to happycommit's config file +# Add the OPENAI_API_KEY to happycommit's config file +# if it isn't there already. +# If it is there, let the user know. + +# First check if the config file exists +if [ ! -f "$HOME/.happycommit/config.toml" ]; then + printf "Creating config file at $HOME/.happycommit/config.toml\n" + mkdir -p "$HOME/.happycommit" + touch "$HOME/.happycommit/config.toml" +fi + +OPENAI_API_KEY="" +# Check if the OPENAI_API_KEY is already in the config file +if ! grep -qF "OPENAI_API_KEY" "$HOME/.happycommit/config.toml"; then + # Read the OPENAI_API_KEY from the user + printf "Please enter your OPENAI_API_KEY (you can get it at https://beta.openai.com/account/api-keys):\nNote: Your input will not be shown on the screen.\n> " + read -rs OPENAI_API_KEY + printf "\n" + # Add the OPENAI_API_KEY to the config.toml file + printf "OPENAI_API_KEY = \"%s\"\n" "$OPENAI_API_KEY" >> "$HOME/.happycommit/config.toml" +else + printf "OPENAI_API_KEY already exists in $HOME/.happycommit/config.toml\n" + printf "Would you like to update it? (y/n) " + read -r update + if [ "$update" = "y" ]; then + # TODO: Remove code duplication + printf "Please enter your OPENAI_API_KEY (you can get it at https://beta.openai.com/account/api-keys):\nNote: Your input will not be shown on the screen.\n> " + read -rs OPENAI_API_KEY + printf "\n" + # Remove the previous OPENAI_API_KEY from the config file + if [ "$machine" = "Mac" ]; then + sed -i '' '/OPENAI_API_KEY/d' "$HOME/.happycommit/config.toml" + else + sed -i '/OPENAI_API_KEY/d' "$HOME/.happycommit/config.toml" + fi + # Add the OPENAI_API_KEY to the config file + printf "OPENAI_API_KEY = \"%s\"\n" "$OPENAI_API_KEY" >> "$HOME/.happycommit/config.toml" + fi +fi + +# Done! +printf "git commit-gpt installed successfully. You can now use 'git commit-gpt' to run happycommit.\n" From 0c8db45682980ca9be2defb23b04750ab779e4d8 Mon Sep 17 00:00:00 2001 From: John Backes Date: Mon, 17 Apr 2023 17:44:30 -0700 Subject: [PATCH 2/4] Add dependencies for config management This commit adds several dependencies in the project, including `dirs` and `toml`, and installs with their latest versions. Full list of updates: - `dirs` has been added at version 5.0.0 - `toml` has been added at version 0.7.3 All existing tests have passed successfully after updating dependencies. Note: No code changes were made in this commit. --- Cargo.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ 2 files changed, 87 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e65c54b..7901904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dirs" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +dependencies = [ + "libc", + "redox_users", + "windows-sys 0.45.0", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -416,6 +436,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-openai", + "dirs", "dotenv", "futures", "lazy_static", @@ -424,6 +445,7 @@ dependencies = [ "tempfile", "tiktoken-rs", "tokio", + "toml", ] [[package]] @@ -807,6 +829,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + [[package]] name = "regex" version = "1.7.3" @@ -1041,6 +1074,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1266,6 +1308,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1647,6 +1723,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index f89ddc7..216628a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ futures = "0.3.28" stream-reduce = "0.1.0" tempfile = "3.5.0" shell-escape = "0.1.5" +dirs = "5.0.0" +toml = "0.7.3" From 80f0c7a4ceb7c27a3b04e5d6159ef5e616847d1b Mon Sep 17 00:00:00 2001 From: John Backes Date: Mon, 17 Apr 2023 17:46:37 -0700 Subject: [PATCH 3/4] Add 'install' command to Justfile The 'install' command has been added to the Justfile. This command builds and installs the binary using 'cargo install' command. It is now easier for users to quickly install the binary and start using it without having to manually build it from source. No testing was done for this commit. Note that the other tasks in the Justfile, 'format' and 'clippy', can now be run using the 'fix' command, as described in the file. --- justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/justfile b/justfile index 012d7ef..f1c4b50 100644 --- a/justfile +++ b/justfile @@ -10,5 +10,9 @@ format: clippy: @cargo clippy +# Install the binary +install: + @cargo install --path . + # Run both 'format' and 'clippy' tasks fix: format clippy \ No newline at end of file From 8cccb58eadf2b1038e36e6d3964eaa61687d6ad7 Mon Sep 17 00:00:00 2001 From: John Backes Date: Mon, 17 Apr 2023 17:47:53 -0700 Subject: [PATCH 4/4] Enhanced OpenAI API key loading This commit enhances the loading of the OpenAI API key in the codebase. Previously, it could only be loaded from a .env file. Now it can also be loaded from the .happycommit/config.toml file, which is used by the install script. However, if it is not found in either location, the code will throw an error. With this commit, we have created a function `load_api_key()` that first checks the `.happycommit/config.toml` file, and then checks the `.env` file for the API key. This ensures that the OpenAI API key can be easily loaded and used in the code. In addition to this, we have fixed a clippy warning regarding the use of unnecessary borrowing in the `stream_multipart_commit_message` function. Testing: - Tested that the OpenAI API key could be loaded from both the `.env` file and the `config.toml` file. - Verified that the app quits when the API key is not found in either file. - Ran all tests and ensured that they passed. Changes: - Created `load_api_key()` function to handle loading of OpenAI API key. - Fixed clippy warning regarding unnecessary borrowing in `stream_multipart_commit_message` function. --- src/main.rs | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5a1e787..bd01929 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ pub struct ChatCompletionRequestMessage { pub role: String, } +#[allow(clippy::from_over_into)] // TODO: fix this impl Into for ChatCompletionRequestMessage { fn into(self) -> tiktoken_rs::ChatCompletionRequestMessage { tiktoken_rs::ChatCompletionRequestMessage { @@ -40,6 +41,7 @@ impl From for ChatCompletionRequestMe } } +#[allow(clippy::from_over_into)] // TODO: fix this impl Into for ChatCompletionRequestMessage { fn into(self) -> async_openai::types::ChatCompletionRequestMessage { async_openai::types::ChatCompletionRequestMessage { @@ -70,6 +72,7 @@ pub struct ChatCompletionRequestMessages { pub messages: Vec, } +#[allow(clippy::from_over_into)] // TODO: fix this impl Into> for ChatCompletionRequestMessages { fn into(self) -> Vec { self.messages @@ -221,10 +224,46 @@ async fn send_to_openai( type ChatMessage = (String, async_openai::types::Role, String); +fn load_api_key() -> Result> { + // first check in ~/.happycommit/config.toml + let happycommitconfig_checker = || -> Result> { + let config_path = dirs::home_dir().unwrap().join(".happycommit/config.toml"); + let config = std::fs::read_to_string(config_path)?; + let config: toml::Value = toml::from_str(&config)?; + let openai_api_key = config.get("openai_api_key"); + if openai_api_key.is_none() { + return Err("OPENAI_API_KEY not set in ~/.happycommit/config.toml".into()); + } + Ok(openai_api_key.unwrap().to_string()) + }; + let dotenv_checker = || -> Result> { + dotenv::dotenv().ok(); + let openai_api_key = + dotenv::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set in .env file"); + Ok(openai_api_key) + }; + + // first check happycommit config, then check dotenv - if in no places, throw + let result = happycommitconfig_checker(); + if result.is_ok() { + return result; + } + let result = dotenv_checker(); + if result.is_ok() { + return result; + } + // throw an error + + Err("OPENAI_API_KEY must be set in .env file or ~/.happycommit/config.toml".into()) +} + #[tokio::main(flavor = "current_thread")] async fn main() { - dotenv::dotenv().ok(); - let openai_api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set"); + let openai_api_key = load_api_key() + .map_err(|e| { + panic!("Error loading OpenAI API key: {}", e); + }) + .unwrap(); // by default, read in all the code changes since origin/master // TODO: allow user to specify a different origin branch or commit @@ -360,7 +399,7 @@ async fn main() { let commit_message = format!("{}\n\n{}", subject, body); let mut commit_file = tempfile::NamedTempFile::new().expect("Failed to create temporary file"); - commit_message.split("\n").for_each(|line| { + commit_message.split('\n').for_each(|line| { let _ = writeln!(commit_file, "{}", line); }); @@ -387,6 +426,7 @@ async fn main() { use anyhow::Result; +#[allow(clippy::needless_borrow)] async fn stream_multipart_commit_message( client: &Client, initial_prompt: &str,