Aller au contenu

Message Git Commit

AI generated translation.

Le script gcm propose un flux guidé pour créer des commits avec des messages générés. Il commence par générer un message de commit basé sur les changements mis en scène dans votre dépôt Git en utilisant un prompt en ligne, puis demande à l’utilisateur de valider le commit ou de régénérer le message.

compute diff
loop
generate commit message
ask user to commit, edit message or regenerate
if user says commit
git commit and push

Tout d’abord, nous définissons la fonction script, qui configure notre script GenAI en fournissant un titre et une description, et en spécifiant le modèle que nous allons utiliser :

script({
title: "git commit message",
description: "Generate a commit message for all staged changes",
model: "openai:gpt-4o",
})

Ensuite, nous vérifions la présence de modifications mises en scène dans votre dépôt Git à l’aide de git diff. S’il n’y a rien de mis en scène, GenAI propose gentiment de tout mettre en scène pour vous :

// 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")

Nous affichons ensuite la différence dans la console pour que vous puissiez vérifier les modifications qui vont être validées :

console.log(diff.stdout)

Voici la partie intéressante. Nous entrons dans une boucle où GenAI génère un message de commit pour vous sur la base du diff. Si le message ne vous satisfait pas, vous pouvez choisir de le modifier, de l’accepter ou de le régénérer :

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 gitmojis`
})
// ... handle response and user choices
} while (choice !== "commit")

Si vous choisissez de valider, GenAI exécute la commande git commit avec votre message, et si vous êtes très confiant, il peut même pousser les modifications vers votre dépôt juste après :

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)
}

Utilisez le cli pour exécuter le script :

Fenêtre de terminal
npx genaiscript run gcm
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",
unlisted: true,
parameters: {
chunkSize: {
type: "number",
default: 10000,
description: "Maximum number of tokens per chunk",
},
maxChunks: {
type: "number",
default: 4,
description:
"Safeguard against huge commits. Asks 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;
// Check for staged changes and stage all changes if none are staged
const diff = await git.diff({
staged: true,
askStageOnEmpty: true,
ignoreSpaceChange: 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.debug(diff);
await git.pull();
// chunk if case of massive diff
const 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 gitPush = async () => {
if (await host.confirm("Push changes?", { default: true })) console.log(await git.exec("push"));
};
const addInstructions = (ctx) => {
ctx.$`
<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 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",
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.fences?.[0]?.content || 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 = 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 in git editor",
},
{
value: "regenerate",
description: "run LLM generation again",
},
{
value: "cancel",
description: "cancel commit",
},
]);
// Handle user's choice for commit message
if (choice === "edit") {
// @ts-ignore
const { spawnSync } = await import("child_process");
// 1) Launch git commit in an interactive editor
const spawnResult = spawnSync("git", ["commit", "-m", message, "--edit"], {
stdio: "inherit",
});
// 2) After the editor closes, forcibly exit the entire script
console.debug("git editor closed with exit code ", spawnResult.status);
if (spawnResult.status === 0) await gitPush();
break;
}
// 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]));
await gitPush();
break;
}
if (choice === "cancel") {
cancel("User cancelled commit");
break;
}
} while (choice !== "commit");

Les mesures suivantes sont prises pour garantir la sécurité du contenu généré.

  • Ce script inclut des invites système pour empêcher les injections de prompt et la génération de contenu nuisible.
  • Le message de commit est examiné et approuvé par l’utilisateur avant de valider les modifications.

Des mesures supplémentaires pour renforcer la sécurité incluent l’exécution d’un modèle avec un filtre de sécurité ou la validation du message via un service de sécurité de contenu.

Consultez la Note de transparence pour plus d’informations sur la sécurité du contenu.