Skip to content

Git Commit Message

The gcm script provides a guided flow to create commit 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 regenerate the message.

compute diff
loop
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 staged
const 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 choice
let message
do {
// 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 emojis`
})
// ... 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:

Terminal window
npx genaiscript run gcm

Full source (GitHub)

gcm.genai.mts
/**
* 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",
system: ["system.assistant"],
})
// Check for staged changes and stage all changes if none are staged
const diff = await git.diff({
staged: true,
askStageOnEmpty: true,
})
// If no staged changes are found, cancel the script with a message
if (!diff) cancel("no staged changes")
// Display the diff of staged changes in the console
console.log(diff)
// chunk if case of massive diff
const chunks = await tokenizers.chunk(diff, { chunkSize: 10000 })
if (chunks.length > 1)
console.log(`staged changes chunked into ${chunks.length} parts`)
let choice
let message
do {
// 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" })
_.$`Generate a git conventional commit message that summarizes the changes in GIT_DIFF.
<type>: <description>
- <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
- GIT_DIFF is generated by "git diff"
- do NOT use markdown syntax
- do NOT add quotes, single quote or code blocks
- keep it short, 1 line only, maximum 50 characters
- use gitmoji
- 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 '+'
`
},
{
model: "large", // Specifies the LLM model to use for message generation
label: "generate commit message", // Label for the prompt task
system: [
"system.safety_jailbreak",
"system.safety_harmful_content",
"system.safety_ungrounded_content_summarization",
],
}
)
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 prompt`Generate a git conventional commit message that summarizes the COMMIT_MESSAGES.
<type>: <description>
- <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
- do NOT use markdown syntax
- do NOT add quotes or code blocks
- keep it short, 1 line only, maximum 50 characters
- use gitmoji
- 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 '+'
- do NOT respond anything else than the commit message
COMMIT_MESSAGES:
${message}
`.options({
model: "large",
label: "summarize chunk commit messages",
system: [
"system.safety_jailbreak",
"system.safety_harmful_content",
"system.safety_ungrounded_content_summarization",
],
})
if (res.error) throw res.error
message = res.text
}
message = 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")