git_commit_ai.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import subprocess
  2. import os
  3. import google.generativeai as genai
  4. def get_staged_diff():
  5. """
  6. Retrieves the diff of staged files using git.
  7. Returns:
  8. str: The diff of the staged files, or None on error.
  9. """
  10. try:
  11. # Use subprocess.run for better control and error handling
  12. process = subprocess.run(
  13. ["git", "diff", "--staged"], # Corrected: --staged is the correct option
  14. capture_output=True,
  15. text=True, # Ensure output is returned as text
  16. check=True, # Raise an exception for non-zero exit codes
  17. )
  18. return process.stdout
  19. except subprocess.CalledProcessError as e:
  20. print(f"Error getting staged diff: {e}")
  21. print(f" stderr: {e.stderr}") # Print stderr for more details
  22. return None
  23. except FileNotFoundError:
  24. print(
  25. "Error: git command not found. Please ensure Git is installed and in your PATH."
  26. )
  27. return None
  28. except Exception as e:
  29. print(f"An unexpected error occurred: {e}")
  30. return None
  31. def generate_commit_message(diff, gemini_api_key):
  32. """
  33. Generates a commit message using the Gemini API, given the diff.
  34. Args:
  35. diff (str): The diff of the staged files.
  36. gemini_api_key (str): Your Gemini API key.
  37. Returns:
  38. str: The generated commit message, or None on error.
  39. """
  40. if not diff:
  41. print("Error: No diff provided to generate commit message.")
  42. return None
  43. genai.configure(api_key=gemini_api_key)
  44. model = genai.GenerativeModel("gemini-2.0-flash")
  45. prompt = f"""
  46. You are a helpful assistant that generates Git commit messages.
  47. Analyze the following diff of staged files and generate a commit message adhering to standard Git conventions:
  48. 1. **Subject Line:** Write a concise, imperative subject line summarizing the change (max 50 characters). Start with a capital letter. Do not end with a period.
  49. 2. **Blank Line:** Leave a single blank line between the subject and the body.
  50. 3. **Body:** Write a detailed but precise body explaining the 'what' and 'why' of the changes. Wrap lines at 72 characters. Focus on the motivation for the change and contrast its implementation with the previous behavior.
  51. Diff:
  52. ```diff
  53. {diff}
  54. ```
  55. Generate only the commit message (subject and body).
  56. """
  57. try:
  58. response = model.generate_content(prompt)
  59. # Check for a successful response.
  60. if response and response.text:
  61. return response.text.strip() # Remove leading/trailing whitespace
  62. else:
  63. print("Error: Gemini API returned an empty or invalid response.")
  64. return None
  65. except Exception as e:
  66. print(f"Error generating commit message with Gemini: {e}")
  67. return None
  68. def create_commit(message):
  69. """
  70. Creates a git commit with the given message.
  71. Args:
  72. message (str): The commit message.
  73. Returns:
  74. bool: True if the commit was successful, False otherwise.
  75. """
  76. if not message:
  77. print("Error: No commit message provided to create commit.")
  78. return False
  79. try:
  80. process = subprocess.run(
  81. ["git", "commit", "-m", message],
  82. check=True, # Important: Raise exception on non-zero exit
  83. capture_output=True, # capture the output
  84. text=True,
  85. )
  86. print(process.stdout) # print the output
  87. return True
  88. except subprocess.CalledProcessError as e:
  89. print(f"Error creating git commit: {e}")
  90. print(e.stderr)
  91. return False
  92. except FileNotFoundError:
  93. print("Error: git command not found. Is Git installed and in your PATH?")
  94. return False
  95. except Exception as e:
  96. print(f"An unexpected error occurred: {e}")
  97. return False
  98. def main():
  99. """
  100. Main function to orchestrate the process of:
  101. 1. Getting the staged diff.
  102. 2. Generating a commit message using Gemini.
  103. 3. Creating a git commit with the generated message.
  104. """
  105. gemini_api_key = os.environ.get("GEMINI_API_KEY")
  106. if not gemini_api_key:
  107. print(
  108. "Error: GEMINI_API_KEY environment variable not set.\n"
  109. " Please obtain an API key from Google Cloud and set the environment variable.\n"
  110. " For example: export GEMINI_API_KEY='YOUR_API_KEY'"
  111. )
  112. return
  113. diff = get_staged_diff()
  114. if diff is None:
  115. print("Aborting commit due to error getting diff.")
  116. return # Exit the script
  117. if not diff.strip(): # check if the diff is empty
  118. print("Aborting: No changes staged to commit.")
  119. return
  120. message = generate_commit_message(diff, gemini_api_key)
  121. if message is None:
  122. print("Aborting commit due to error generating message.")
  123. return # Exit if message generation failed
  124. print(f"Generated commit message:\n{message}") # Print the message for review
  125. # Prompt the user for confirmation before committing
  126. user_input = input(
  127. "Do you want to create the commit with this message? (y/n): "
  128. ).lower()
  129. if user_input == "y":
  130. if create_commit(message):
  131. print("Commit created successfully.")
  132. else:
  133. print("Commit failed.")
  134. else:
  135. print("Commit aborted by user.")
  136. if __name__ == "__main__":
  137. main()