Git Commit Message
The gcm
script provides a guided flow to create commits with generated messages.
It starts by generating a commit message based on the staged changes in your Git repository
using an inline prompt
and then asks the user to commit the changes or to regenerate the message.
compute diffloop generate commit message ask user to commit, edit message or regenerate if user says commit git commit and push
Configuration
First off, we define the script
function, which sets up our GenAI script by providing a title and a description, and specifying the model we’ll be using:
script({ title: "git commit message", description: "Generate a commit message for all staged changes", model: "openai:gpt-4o",})
Look for changes
Next up, we check for any staged changes in your Git repository using git diff
.
If there’s nothing staged, GenAI kindly offers to stage all changes for you:
// Check for staged changes and stage all changes if none are stagedconst diff = await git.diff({ staged: true, askStageOnEmpty: true,})if (!diff) cancel("no staged changes")
We then log the diff to the console so you can review what changes are about to be committed:
console.log(diff.stdout)
Generate and refine commit message
Here comes the interesting part. We enter a loop where GenAI will generate a commit message for you based on the diff. If you’re not satisfied with the message, you can choose to edit it, accept it, or regenerate it:
let choicelet messagedo { // generate a conventional commit message (https://www.conventionalcommits.org/en/v1.0.0/) const res = await runPrompt((_) => { _.def("GIT_DIFF", diff, { maxTokens: 20000, language: "diff" }) _.$`Generate a git conventional commit message for the changes in GIT_DIFF. - do NOT add quotes - maximum 50 characters - use gitmojis` }) // ... handle response and user choices} while (choice !== "commit")
Commit and push
If you choose to commit, GenAI runs the git commit
command with your message, and if you’re feeling super confident, it can even push the changes to your repository right after:
if (choice === "commit" && message) { console.log( (await host.exec("git", ["commit", "-m", message, "-n"])).stdout ) if (await host.confirm("Push changes?", { default: true })) console.log((await host.exec("git push")).stdout)}
Running the Script with GenAIScript CLI
Use the cli to run the script:
npx genaiscript run gcm
Full source (GitHub)
/** * Script to automate the git commit process with AI-generated commit messages. * It checks for staged changes, generates a commit message, and prompts the user to review or edit the message before committing. */
script({ title: "git commit message", description: "Generate a commit message for all staged changes", parameters: { chunkSize: { type: "number", default: 10000, description: "Maximum number of tokens per chunk", }, maxChunks: { type: "number", default: 4, description: "Safeguard against huge commits. Askes confirmation to the user before running more than maxChunks chunks", }, gitmoji: { type: "boolean", default: true, description: "Use gitmoji in the commit message", }, },})const { chunkSize, maxChunks, gitmoji } = env.vars
console.debug(`config: ${JSON.stringify({ chunkSize, maxChunks, gitmoji })}`)
// Check for staged changes and stage all changes if none are stagedconst diff = await git.diff({ staged: true, askStageOnEmpty: true,})
// If no staged changes are found, cancel the script with a messageif (!diff) cancel("no staged changes")
// Display the diff of staged changes in the consoleconsole.debug(diff)
// chunk if case of massive diffconst chunks = await tokenizers.chunk(diff, { chunkSize })if (chunks.length > 1) { console.log(`staged changes chunked into ${chunks.length} parts`) if (chunks.length > maxChunks) { const res = await host.confirm( `This is a big diff with ${chunks.length} chunks, do you want to proceed?` ) if (!res) cancel("user cancelled") }}
const addInstructions = (_) => { _.$`
<type>: <description><body>
${gitmoji ? `- <type> is a gitmoji` : `- <type> can be one of the following: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert`}- <description> is a short, imperative present-tense description of the change- <body> is a short description of the changes- Pretend you're writing an important newsworthy article. Give the headline in <description> that will sum up what happened and what is important. Then, provide further details in the <body> in an organized fashion.- the diff is generated by "git diff"- do NOT use markdown syntax- do NOT add quotes, single quote or code blocks- keep <description> short, 1 LINE ONLY, maximum 50 characters- keep <body> short, 1 LINE ONLY, maximum 72 characters- follow the conventional commit spec at https://www.conventionalcommits.org/en/v1.0.0/#specification- do NOT confuse delete lines starting with '-' and add lines starting with '+'`}
let choicelet messagedo { // Generate a conventional commit message based on the staged changes diff message = "" for (const chunk of chunks) { const res = await runPrompt( (_) => { _.def("GIT_DIFF", chunk, { maxTokens: 10000, language: "diff", detectPromptInjection: "available", }) _.$`Generate a git conventional commit message that summarizes the changes in GIT_DIFF.` addInstructions(_) }, { model: "large", // Specifies the LLM model to use for message generation label: "generate commit message", // Label for the prompt task system: ["system.assistant"], systemSafety: true, responseType: "text", } ) if (res.error) throw res.error message += res.text + "\n" }
// since we've concatenated the chunks, let's compress it back into a single sentence again if (chunks.length > 1) { const res = await runPrompt( (_) => { _.$`Generate a git conventional commit message that summarizes the <COMMIT_MESSAGES>.` addInstructions(_) _.def("COMMIT_MESSAGES", message) }, { model: "large", label: "summarize chunk commit messages", system: ["system.assistant"], systemSafety: true, responseType: "text", } ) if (res.error) throw res.error message = res.text }
message = parsers.unthink(message?.trim()) if (!message) { console.log( "No commit message generated, did you configure the LLM model?" ) break }
// Prompt user to accept, edit, or regenerate the commit message choice = await host.select(message, [ { value: "commit", description: "accept message and commit", }, { value: "edit", description: "edit message and commit", }, { value: "regenerate", description: "regenerate message", }, ])
// Handle user's choice for commit message if (choice === "edit") { message = await host.input("Edit commit message", { required: true, }) choice = "commit" } // If user chooses to commit, execute the git commit and optionally push changes if (choice === "commit" && message) { console.log(await git.exec(["commit", "-m", message])) if (await host.confirm("Push changes?", { default: true })) console.log(await git.exec("push")) break }} while (choice !== "commit")
Content Safety
The following measures are taken to ensure the safety of the generated content.
- This script includes system prompts to prevent prompt injection and harmful content generation.
- The commit message is reviewed and approved by the user before committing the changes.
Additional measures to further enhance safety would be to run a model with a safety filter or validate the message with a content safety service.
Refer to the Transparency Note for more information on content safety.