git_ui: Scaffold out support for generating commit messages with an LLM (#26161)

This PR adds the rough structure needed to support generating commit
messages using an LLM.

This functionality is not yet surfaced to the user.

This is the current state, if you tweak the source to show the button:


https://github.com/user-attachments/assets/66d1fbc4-09f3-4277-84f4-e9c9ebab274c

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-03-05 16:42:48 -05:00 committed by GitHub
parent 6a3e8044b1
commit e99d68a66f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 0 deletions

View file

@ -12,6 +12,7 @@ use editor::{
scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer,
ShowScrollbar,
};
use futures::StreamExt as _;
use git::repository::{
Branch, CommitDetails, CommitSummary, PushOptions, Remote, RemoteCommandOutput, ResetMode,
Upstream, UpstreamTracking, UpstreamTrackingStatus,
@ -21,6 +22,9 @@ use git::{RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
use gpui::*;
use itertools::Itertools;
use language::{Buffer, File};
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
use multi_buffer::ExcerptInfo;
use panel::{
@ -195,6 +199,7 @@ pub struct GitPanel {
conflicted_staged_count: usize,
current_modifiers: Modifiers,
add_coauthors: bool,
generate_commit_message_task: Option<Task<Option<()>>>,
entries: Vec<GitListEntry>,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
@ -318,6 +323,7 @@ impl GitPanel {
conflicted_staged_count: 0,
current_modifiers: window.modifiers(),
add_coauthors: true,
generate_commit_message_task: None,
entries: Vec::new(),
focus_handle: cx.focus_handle(),
fs,
@ -1346,6 +1352,71 @@ impl GitPanel {
Some(format!("{} {}", action_text, file_name))
}
/// Generates a commit message using an LLM.
fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
if !provider.is_authenticated(cx) {
return;
}
const PROMPT: &str = include_str!("commit_message_prompt.txt");
// TODO: We need to generate a diff from the actual Git state.
//
// It need not look exactly like the structure below, this is just an example generated by Claude.
let diff_text = "diff --git a/src/main.rs b/src/main.rs
index 1234567..abcdef0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,7 +10,7 @@ fn main() {
println!(\"Hello, world!\");
- let unused_var = 42;
+ let important_value = 42;
// Do something with the value
- // TODO: Implement this later
+ println!(\"The answer is {}\", important_value);
}";
let request = LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: vec![format!("{PROMPT}\n{diff_text}").into()],
cache: false,
}],
tools: Vec::new(),
stop: Vec::new(),
temperature: None,
};
self.generate_commit_message_task = Some(cx.spawn(|this, mut cx| {
async move {
let stream = model.stream_completion_text(request, &cx);
let mut messages = stream.await?;
while let Some(message) = messages.stream.next().await {
let text = message?;
this.update(&mut cx, |this, cx| {
this.commit_message_buffer(cx).update(cx, |buffer, cx| {
let insert_position = buffer.anchor_before(buffer.len());
buffer.edit([(insert_position..insert_position, text)], None, cx);
});
})?;
}
anyhow::Ok(())
}
.log_err()
}));
}
fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) {
let suggested_commit_message = self.suggest_commit_message();
let placeholder_text = suggested_commit_message
@ -2013,6 +2084,8 @@ impl GitPanel {
let panel_editor_style = panel_editor_style(true, window, cx);
let enable_coauthors = self.render_co_authors(cx);
// Note: This is hard-coded to `false` as it is not fully implemented.
let show_generate_commit_message_button = false;
let title = self.commit_button_title();
let editor_focus_handle = self.commit_editor.focus_handle(cx);
@ -2060,8 +2133,18 @@ impl GitPanel {
.absolute()
.bottom_0()
.right_2()
.gap_0p5()
.h(footer_size)
.flex_none()
.when(show_generate_commit_message_button, |parent| {
parent.child(
panel_filled_button("Generate Commit Message").on_click(
cx.listener(move |this, _event, _window, cx| {
this.generate_commit_message(cx);
}),
),
)
})
.children(enable_coauthors)
.child(
panel_filled_button(title)