Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
102 changes: 102 additions & 0 deletions install-commit-gpt.sh
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ format:
clippy:
@cargo clippy

# Install the binary
install:
@cargo install --path .

# Run both 'format' and 'clippy' tasks
fix: format clippy
46 changes: 43 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct ChatCompletionRequestMessage {
pub role: String,
}

#[allow(clippy::from_over_into)] // TODO: fix this
impl Into<tiktoken_rs::ChatCompletionRequestMessage> for ChatCompletionRequestMessage {
fn into(self) -> tiktoken_rs::ChatCompletionRequestMessage {
tiktoken_rs::ChatCompletionRequestMessage {
Expand All @@ -40,6 +41,7 @@ impl From<tiktoken_rs::ChatCompletionRequestMessage> for ChatCompletionRequestMe
}
}

#[allow(clippy::from_over_into)] // TODO: fix this
impl Into<async_openai::types::ChatCompletionRequestMessage> for ChatCompletionRequestMessage {
fn into(self) -> async_openai::types::ChatCompletionRequestMessage {
async_openai::types::ChatCompletionRequestMessage {
Expand Down Expand Up @@ -70,6 +72,7 @@ pub struct ChatCompletionRequestMessages {
pub messages: Vec<ChatCompletionRequestMessage>,
}

#[allow(clippy::from_over_into)] // TODO: fix this
impl Into<Vec<tiktoken_rs::ChatCompletionRequestMessage>> for ChatCompletionRequestMessages {
fn into(self) -> Vec<tiktoken_rs::ChatCompletionRequestMessage> {
self.messages
Expand Down Expand Up @@ -221,10 +224,46 @@ async fn send_to_openai(

type ChatMessage = (String, async_openai::types::Role, String);

fn load_api_key() -> Result<String, Box<dyn std::error::Error>> {
// first check in ~/.happycommit/config.toml
let happycommitconfig_checker = || -> Result<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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
Expand Down Expand Up @@ -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);
});

Expand All @@ -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,
Expand Down