R/commit.R

Defines functions suggest_commit_message add_commit_push generate_commit_message generate_encoded_git_diff_output generate_git_diff_output commit

Documented in add_commit_push commit generate_commit_message generate_encoded_git_diff_output generate_git_diff_output suggest_commit_message

#' Commit changes using an auto-generated commit message
#'
#' An R wrapper to automatically add, commit, and push changes
#' to a git repository using an auto-generated commit message. The commit
#' message is generated by an API call to the 'OpenAI' 'GPT-3.5 Turbo' model
#' using the git diff output as input.
#'
#' @param commit_message A custom commit message. If not provided, a message
#'     will be generated automatically.
#' @param prepend A string to prepend to the commit message. Defaults to an
#'     empty string. A convention may
#'     be to prepend 'GPT: ' to the beginning of the commit message so you can
#'     differentiate between those you wrote vs those GPT composed.
#' @return No return value, called for side effects.
#' @export
#' @examples
#' \dontrun{
#'   # Simple command to git add, git commit, and git push with a commit
#'   # message based on git diff of the working directory
#'   commit()
#' }
#'
commit <- function(commit_message, prepend) { # alias commit_GPT() commit_message

  # Work out which OS
  os <- Sys.info()['sysname']

  # Take git diff
  git_diff_output <- generate_git_diff_output()

  # Encode it
  encoded_git_diff_output <- generate_encoded_git_diff_output(git_diff_output)

  # Make commit message
  encoded_git_diff_output <- substr(encoded_git_diff_output, 1, 4000)
  if(missing(commit_message)) { commit_message <- generate_commit_message(encoded_git_diff_output) }

  # Add, commit and push
  # sink("/dev/null")
  # on.exit(sink())
  add_commit_push(commit_message)
  # if(exists("output")) { output }

}






#' Generate git diff output
#'
#' Returns the git diff output for the current working directory, and lists
#' any new files found.
#' It checks the system's OS and executes the appropriate command to generate
#' the git diff.
#'
#' @return A character vector containing the git diff output.
#' @export
#' @examples
#' \dontrun{
#'   # View with cat() for easier reading
#'   cat(generate_git_diff_output())
#' }
generate_git_diff_output <- function() {

  # See: https://r-pkgs.org/misc.html#sec-misc-inst
  windows_script_path <- system.file("git_diff_and_new_files.bat", package = "gitGPT")
  nix_script_path <- system.file("git_diff_and_new_files.sh", package = "gitGPT")

  os <- Sys.info()['sysname']

  if(os == "Windows") {

    git_diff_output <- paste0(system2(windows_script_path, stdout = TRUE), collapse = "\n")

  } else {

    git_diff_and_new_files <- paste0(
      readLines(nix_script_path),
      collapse = "\n")
    git_diff_output <- paste0(system(git_diff_and_new_files, intern = TRUE), collapse = "\n")

    }

  git_diff_output
}







#' Encode git diff output
#'
#' Takes the git diff output and encodes it for use in the API
#' call to the 'OpenAI' 'GPT-3.5 Turbo' model.
#'
#' @param git_diff_output A character vector containing the git diff output.
#' @return A character vector containing the URL-encoded git diff output.
#' @export
#' @examples
#' \dontrun{
#'   git_diff_output <- generate_git_diff_output()
#'   generate_encoded_git_diff_output(git_diff_output)
#' }
generate_encoded_git_diff_output <- function(git_diff_output) {
  utils::URLencode(git_diff_output)
}








#' Generate commit message
#'
#' Generates a commit message by making an API call to the 'OpenAI'
#' 'GPT-3.5 Turbo' model using the
#' encoded git diff output as input.
#'
#' @param encoded_git_diff_output A character vector containing the URL-encoded
#'     git diff output.
#' @return A character vector of length 1 containing the generated commit
#'     message.
#' @export
#' @examples
#' \dontrun{
#'   # Sends the encoded git diff to GPT and returns a
#'   # character vector containing the commit message:
#'   git_diff_output <- generate_git_diff_output()
#'   edo <- generate_encoded_git_diff_output(git_diff_output)
#'   enerate_commit_message(edo)
#' }
generate_commit_message <- function(encoded_git_diff_output) {
  # See: https://github.com/curlconverter/curlconverter
  # Also: https://curlconverter.com/r/

  api_key_not_found_error_message <- '"OPENAI_API_KEY" environment variable not found.

  Find it here:

  https://help.openai.com/en/articles/4936850-where-do-i-find-my-secret-api-key

  Set it in your R session with:

  `Sys.setenv(OPENAI_API_KEY=\"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\")`

  Or add it to your .Renviron, .zshenv, .bashrc file.'


  openai_api_key <- Sys.getenv("OPENAI_API_KEY")
  if(openai_api_key == "") { stop(api_key_not_found_error_message)}

  headers = c(
    `Content-Type` = "application/json",
    `Authorization` = paste("Bearer ", openai_api_key, sep = "")
  )

  data = paste('{\n    "model": "gpt-3.5-turbo",\n    "messages": [{"role": "user", "content": "Can you give a short commit message (under 50 words) that summarises the changes represented by this `git diff` output: ', encoded_git_diff_output, '"}],\n    "temperature": 0.7\n  }', sep = "")

  response <- httr::POST(url = "https://api.openai.com/v1/chat/completions", httr::add_headers(.headers=headers), body = data)

  commit_message <- jsonlite::fromJSON(readBin(response$content, "character"))$choices$message$content
  commit_message
}












#' Add, commit, and push changes to a Git repository
#'
#' Aautomates the process of adding changes to a Git repository,
#' committing
#' those changes with a commit message, and pushing the changes to a remote
#' repository.
#'
#' @param commit_message A character string containing the commit message for
#'     the changes.
#' @param prepend A character string to prepend to the commit message.
#'     Defaults to an empty string.
#' @return No return value, called for side effects.
#' @export
#' @examples
#' \dontrun{
#'   add_commit_push()
#' }
add_commit_push <- function(commit_message, prepend) {

  if(missing(prepend)) {
    if(is.null(options("git_prepend")[[1]])) {
      prepend <- ""
    } else {
      prepend <- options("git_prepend")[[1]]
    }
   }

  commit_message_with_prepend <-paste0(prepend, trimws(commit_message))

  os <- Sys.info()['sysname']
  if(os == "Windows") {

   # Windows
    escaped_commit_message_with_prepend <- gsub("([\"'!@#$%&*()[\\]\\{\\};:/\\\\?\\|])", "\\\\\\1", commit_message_with_prepend)

    system2("git", c("add", "."), stdout=TRUE)

    # Couldn't get git commit to work on windows unless -c (config) flag was provided
    command <- paste0('git -c user.name="', Sys.getenv("GIT_AUTHOR_NAME"),
                      '" -c user.email="',
                      Sys.getenv("GIT_AUTHOR_EMAIL"), '" commit -m "',
                      escaped_commit_message_with_prepend,
                      '"')
    git_commit_output <- system(command, intern=TRUE)

    git_push_output <- system2("git", c("push"), stdout=TRUE)

    output <- c(git_push_output, commit_message_with_prepend)
    # output

  } else {

    # nix
    # Not sure how this will handle single quotes, other chars may need escaping
    command <- paste0(
      "git add . \ngit commit -m '",
      commit_message_with_prepend,
      "'\ngit push")
    # note: ignore.stderr = TRUE was necessary to prevent git push message
    # appearing in R console even though it's not an error.
    output <- system(command, intern = TRUE, ignore.stdout = TRUE, ignore.stderr = TRUE)
    # message(output)
    # system("git reset HEAD~ && git push -f")
  }

}











#' Suggest a commit message based on the provided git diff
#'
#' Suggests a commit message by utilizing the 'OpenAI' 'GPT-3.5
#' Turbo' model.
#' It takes a git diff as input and returns a meaningful
#' commit message.
#'
#' @param diff An optional character vector containing the git diff. If not
#' provided, the function will automatically
#' generate the git diff output for the current working directory.
#' @return A character vector of length 1 with the suggested commit message
#'     based on the provided git diff.
#' @export
#' @examples
#' \dontrun{
#'   suggest_commit_message()
#' }
suggest_commit_message <- function(diff) {

  if(missing(diff)) {
    git_diff_output <- generate_git_diff_output()
    diff <- generate_encoded_git_diff_output(git_diff_output)
  }

  encoded_diff <- utils::URLencode(diff)
  commit_message <- generate_commit_message(diff)
  cat(commit_message)
}

Try the gitGPT package in your browser

Any scripts or data that you put into this service are public.

gitGPT documentation built on April 5, 2023, 5:17 p.m.