git_ui: Add support for generating commit messages with an LLM (#26227)
This PR finishes up the support for generating commit messages using an LLM. We're shelling out to `git diff` to get the diff text, as it seemed more efficient than attempting to reconstruct the diff ourselves from our internal Git state. https://github.com/user-attachments/assets/9bcf30a7-7a08-4f49-a753-72a5d954bddd Release Notes: - Git Beta: Added support for generating commit messages using a language model. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
d1cec209d4
commit
b8a8b9c699
11 changed files with 233 additions and 52 deletions
|
@ -747,7 +747,8 @@
|
||||||
"context": "GitCommit > Editor",
|
"context": "GitCommit > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit"
|
"ctrl-enter": "git::Commit",
|
||||||
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -769,7 +770,8 @@
|
||||||
"tab": "git_panel::FocusChanges",
|
"tab": "git_panel::FocusChanges",
|
||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
"alt-up": "git_panel::FocusChanges"
|
"alt-up": "git_panel::FocusChanges",
|
||||||
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -786,7 +786,8 @@
|
||||||
"tab": "git_panel::FocusChanges",
|
"tab": "git_panel::FocusChanges",
|
||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
"shift-escape": "git::ExpandCommitEditor"
|
"shift-escape": "git::ExpandCommitEditor",
|
||||||
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -794,7 +795,8 @@
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"cmd-enter": "git::Commit"
|
"cmd-enter": "git::Commit",
|
||||||
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -402,6 +402,7 @@ impl Server {
|
||||||
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
|
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::GitDiff>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
|
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
|
||||||
|
|
|
@ -49,7 +49,8 @@ actions!(
|
||||||
Pull,
|
Pull,
|
||||||
Fetch,
|
Fetch,
|
||||||
Commit,
|
Commit,
|
||||||
ExpandCommitEditor
|
ExpandCommitEditor,
|
||||||
|
GenerateCommitMessage
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]);
|
action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]);
|
||||||
|
|
|
@ -217,6 +217,14 @@ pub trait GitRepository: Send + Sync {
|
||||||
|
|
||||||
/// returns a list of remote branches that contain HEAD
|
/// returns a list of remote branches that contain HEAD
|
||||||
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>>;
|
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>>;
|
||||||
|
|
||||||
|
/// Run git diff
|
||||||
|
fn diff(&self, diff: DiffType) -> Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DiffType {
|
||||||
|
HeadToIndex,
|
||||||
|
HeadToWorktree,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
|
||||||
|
@ -577,6 +585,28 @@ impl GitRepository for RealGitRepository {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff(&self, diff: DiffType) -> Result<String> {
|
||||||
|
let working_directory = self.working_directory()?;
|
||||||
|
let args = match diff {
|
||||||
|
DiffType::HeadToIndex => Some("--staged"),
|
||||||
|
DiffType::HeadToWorktree => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = new_std_command(&self.git_binary_path)
|
||||||
|
.current_dir(&working_directory)
|
||||||
|
.args(["diff"])
|
||||||
|
.args(args)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Failed to run git diff:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn stage_paths(&self, paths: &[RepoPath]) -> Result<()> {
|
fn stage_paths(&self, paths: &[RepoPath]) -> Result<()> {
|
||||||
let working_directory = self.working_directory()?;
|
let working_directory = self.working_directory()?;
|
||||||
|
|
||||||
|
@ -1048,6 +1078,10 @@ impl GitRepository for FakeGitRepository {
|
||||||
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
|
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff(&self, _diff: DiffType) -> Result<String> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
You are an expert at writing Git commits. Your job is to write a clear commit message that summarizes the changes.
|
You are an expert at writing Git commits. Your job is to write a short clear commit message that summarizes the changes.
|
||||||
|
|
||||||
|
If you can accurately express the change in just the subject line, don't include anything in the message body. Only use the body when it is providing *useful* information.
|
||||||
|
|
||||||
|
Don't repeat information from the subject line in the message body.
|
||||||
|
|
||||||
Only return the commit message in your response. Do not include any additional meta-commentary about the task.
|
Only return the commit message in your response. Do not include any additional meta-commentary about the task.
|
||||||
|
|
||||||
|
@ -10,6 +14,6 @@ Follow good Git style:
|
||||||
- Do not end the subject line with any punctuation
|
- Do not end the subject line with any punctuation
|
||||||
- Use the imperative mood in the subject line
|
- Use the imperative mood in the subject line
|
||||||
- Wrap the body at 72 characters
|
- Wrap the body at 72 characters
|
||||||
- Use the body to explain *what* and *why* vs. *how*
|
- Keep the body short and concise (omit it entirely if not useful)
|
||||||
|
|
||||||
Here are the changes in this commit:
|
Here are the changes in this commit:
|
||||||
|
|
|
@ -234,7 +234,7 @@ impl CommitModal {
|
||||||
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let git_panel = self.git_panel.clone();
|
let git_panel = self.git_panel.clone();
|
||||||
|
|
||||||
let (branch, can_commit, tooltip, commit_label, co_authors) =
|
let (branch, can_commit, tooltip, commit_label, co_authors, generate_commit_message) =
|
||||||
self.git_panel.update(cx, |git_panel, cx| {
|
self.git_panel.update(cx, |git_panel, cx| {
|
||||||
let branch = git_panel
|
let branch = git_panel
|
||||||
.active_repository
|
.active_repository
|
||||||
|
@ -249,7 +249,15 @@ impl CommitModal {
|
||||||
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
|
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
|
||||||
let title = git_panel.commit_button_title();
|
let title = git_panel.commit_button_title();
|
||||||
let co_authors = git_panel.render_co_authors(cx);
|
let co_authors = git_panel.render_co_authors(cx);
|
||||||
(branch, can_commit, tooltip, title, co_authors)
|
let generate_commit_message = git_panel.render_generate_commit_message_button(cx);
|
||||||
|
(
|
||||||
|
branch,
|
||||||
|
can_commit,
|
||||||
|
tooltip,
|
||||||
|
title,
|
||||||
|
co_authors,
|
||||||
|
generate_commit_message,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let branch_picker_button = panel_button(branch)
|
let branch_picker_button = panel_button(branch)
|
||||||
|
@ -316,7 +324,13 @@ impl CommitModal {
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(px(self.properties.footer_height))
|
.h(px(self.properties.footer_height))
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(h_flex().gap_1().child(branch_picker).children(co_authors))
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(branch_picker)
|
||||||
|
.children(co_authors)
|
||||||
|
.child(generate_commit_message),
|
||||||
|
)
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
|
@ -18,8 +18,8 @@ use editor::{
|
||||||
};
|
};
|
||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
use git::repository::{
|
use git::repository::{
|
||||||
Branch, CommitDetails, CommitSummary, PushOptions, Remote, RemoteCommandOutput, ResetMode,
|
Branch, CommitDetails, CommitSummary, DiffType, PushOptions, Remote, RemoteCommandOutput,
|
||||||
Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
||||||
};
|
};
|
||||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||||
use git::{RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
use git::{RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||||
|
@ -1393,6 +1393,15 @@ impl GitPanel {
|
||||||
Some(format!("{} {}", action_text, file_name))
|
Some(format!("{} {}", action_text, file_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_commit_message_action(
|
||||||
|
&mut self,
|
||||||
|
_: &git::GenerateCommitMessage,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.generate_commit_message(cx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates a commit message using an LLM.
|
/// Generates a commit message using an LLM.
|
||||||
fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
|
fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||||
|
@ -1406,25 +1415,39 @@ impl GitPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(repo) = self.active_repository.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let diff = repo.update(cx, |repo, cx| {
|
||||||
|
if self.has_staged_changes() {
|
||||||
|
repo.diff(DiffType::HeadToIndex, cx)
|
||||||
|
} else {
|
||||||
|
repo.diff(DiffType::HeadToWorktree, cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.generate_commit_message_task = Some(cx.spawn(|this, mut cx| {
|
||||||
|
async move {
|
||||||
|
let _defer = util::defer({
|
||||||
|
let mut cx = cx.clone();
|
||||||
|
let this = this.clone();
|
||||||
|
move || {
|
||||||
|
this.update(&mut cx, |this, _cx| {
|
||||||
|
this.generate_commit_message_task.take();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut diff_text = diff.await??;
|
||||||
|
const ONE_MB: usize = 1_000_000;
|
||||||
|
if diff_text.len() > ONE_MB {
|
||||||
|
diff_text = diff_text.chars().take(ONE_MB).collect()
|
||||||
|
}
|
||||||
|
|
||||||
const PROMPT: &str = include_str!("commit_message_prompt.txt");
|
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 {
|
let request = LanguageModelRequest {
|
||||||
messages: vec![LanguageModelRequestMessage {
|
messages: vec![LanguageModelRequestMessage {
|
||||||
role: Role::User,
|
role: Role::User,
|
||||||
|
@ -1436,8 +1459,6 @@ index 1234567..abcdef0 100644
|
||||||
temperature: None,
|
temperature: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.generate_commit_message_task = Some(cx.spawn(|this, mut cx| {
|
|
||||||
async move {
|
|
||||||
let stream = model.stream_completion_text(request, &cx);
|
let stream = model.stream_completion_text(request, &cx);
|
||||||
let mut messages = stream.await?;
|
let mut messages = stream.await?;
|
||||||
|
|
||||||
|
@ -2113,6 +2134,32 @@ index 1234567..abcdef0 100644
|
||||||
self.has_staged_changes()
|
self.has_staged_changes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_generate_commit_message_button(&self, cx: &Context<Self>) -> AnyElement {
|
||||||
|
if self.generate_commit_message_task.is_some() {
|
||||||
|
return Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Info)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
)
|
||||||
|
.into_any_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton::new("generate-commit-message", IconName::ZedAssistant)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.tooltip(Tooltip::for_action_title_in(
|
||||||
|
"Generate commit message",
|
||||||
|
&git::GenerateCommitMessage,
|
||||||
|
&self.commit_editor.focus_handle(cx),
|
||||||
|
))
|
||||||
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
|
this.generate_commit_message(cx);
|
||||||
|
}))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn render_co_authors(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
pub(crate) fn render_co_authors(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||||
let potential_co_authors = self.potential_co_authors(cx);
|
let potential_co_authors = self.potential_co_authors(cx);
|
||||||
if potential_co_authors.is_empty() {
|
if potential_co_authors.is_empty() {
|
||||||
|
@ -2201,8 +2248,6 @@ index 1234567..abcdef0 100644
|
||||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||||
|
|
||||||
let enable_coauthors = self.render_co_authors(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 title = self.commit_button_title();
|
||||||
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
||||||
|
@ -2253,16 +2298,8 @@ index 1234567..abcdef0 100644
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.h(footer_size)
|
.h(footer_size)
|
||||||
.flex_none()
|
.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)
|
.children(enable_coauthors)
|
||||||
|
.child(self.render_generate_commit_message_button(cx))
|
||||||
.child(
|
.child(
|
||||||
panel_filled_button(title)
|
panel_filled_button(title)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |window, cx| {
|
||||||
|
@ -2927,6 +2964,7 @@ impl Render for GitPanel {
|
||||||
.on_action(cx.listener(Self::restore_tracked_files))
|
.on_action(cx.listener(Self::restore_tracked_files))
|
||||||
.on_action(cx.listener(Self::clean_all))
|
.on_action(cx.listener(Self::clean_all))
|
||||||
.on_action(cx.listener(Self::expand_commit_editor))
|
.on_action(cx.listener(Self::expand_commit_editor))
|
||||||
|
.on_action(cx.listener(Self::generate_commit_message_action))
|
||||||
.when(has_write_access && has_co_authors, |git_panel| {
|
.when(has_write_access && has_co_authors, |git_panel| {
|
||||||
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
|
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,6 +12,7 @@ use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
StreamExt as _,
|
StreamExt as _,
|
||||||
};
|
};
|
||||||
|
use git::repository::DiffType;
|
||||||
use git::{
|
use git::{
|
||||||
repository::{
|
repository::{
|
||||||
Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath,
|
Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath,
|
||||||
|
@ -136,6 +137,7 @@ impl GitStore {
|
||||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||||
client.add_entity_request_handler(Self::handle_askpass);
|
client.add_entity_request_handler(Self::handle_askpass);
|
||||||
client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
|
client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
|
||||||
|
client.add_entity_request_handler(Self::handle_git_diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_repository(&self) -> Option<Entity<Repository>> {
|
pub fn active_repository(&self) -> Option<Entity<Repository>> {
|
||||||
|
@ -807,6 +809,33 @@ impl GitStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_git_diff(
|
||||||
|
this: Entity<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::GitDiff>,
|
||||||
|
mut cx: AsyncApp,
|
||||||
|
) -> Result<proto::GitDiffResponse> {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||||
|
let repository_handle =
|
||||||
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
|
let diff_type = match envelope.payload.diff_type() {
|
||||||
|
proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
|
||||||
|
proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diff = repository_handle
|
||||||
|
.update(&mut cx, |repository_handle, cx| {
|
||||||
|
repository_handle.diff(diff_type, cx)
|
||||||
|
})?
|
||||||
|
.await??;
|
||||||
|
const ONE_MB: usize = 1_000_000;
|
||||||
|
if diff.len() > ONE_MB {
|
||||||
|
diff = diff.chars().take(ONE_MB).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(proto::GitDiffResponse { diff })
|
||||||
|
}
|
||||||
|
|
||||||
fn repository_for_request(
|
fn repository_for_request(
|
||||||
this: &Entity<Self>,
|
this: &Entity<Self>,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
|
@ -1627,6 +1656,39 @@ impl Repository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
|
||||||
|
self.send_job(|repo| async move {
|
||||||
|
match repo {
|
||||||
|
GitRepo::Local(git_repository) => git_repository.diff(diff_type),
|
||||||
|
GitRepo::Remote {
|
||||||
|
project_id,
|
||||||
|
client,
|
||||||
|
worktree_id,
|
||||||
|
work_directory_id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let response = client
|
||||||
|
.request(proto::GitDiff {
|
||||||
|
project_id: project_id.0,
|
||||||
|
worktree_id: worktree_id.to_proto(),
|
||||||
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
|
diff_type: match diff_type {
|
||||||
|
DiffType::HeadToIndex => {
|
||||||
|
proto::git_diff::DiffType::HeadToIndex.into()
|
||||||
|
}
|
||||||
|
DiffType::HeadToWorktree => {
|
||||||
|
proto::git_diff::DiffType::HeadToWorktree.into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response.diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
|
pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
|
||||||
self.send_job(|repo| async move {
|
self.send_job(|repo| async move {
|
||||||
match repo {
|
match repo {
|
||||||
|
|
|
@ -342,7 +342,10 @@ message Envelope {
|
||||||
CheckForPushedCommitsResponse check_for_pushed_commits_response = 316;
|
CheckForPushedCommitsResponse check_for_pushed_commits_response = 316;
|
||||||
|
|
||||||
AskPassRequest ask_pass_request = 317;
|
AskPassRequest ask_pass_request = 317;
|
||||||
AskPassResponse ask_pass_response = 318; // current max
|
AskPassResponse ask_pass_response = 318;
|
||||||
|
|
||||||
|
GitDiff git_diff = 319;
|
||||||
|
GitDiffResponse git_diff_response = 320; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
@ -2907,3 +2910,19 @@ message CheckForPushedCommits {
|
||||||
message CheckForPushedCommitsResponse {
|
message CheckForPushedCommitsResponse {
|
||||||
repeated string pushed_to = 1;
|
repeated string pushed_to = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GitDiff {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
uint64 work_directory_id = 3;
|
||||||
|
DiffType diff_type = 4;
|
||||||
|
|
||||||
|
enum DiffType {
|
||||||
|
HEAD_TO_WORKTREE = 0;
|
||||||
|
HEAD_TO_INDEX = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GitDiffResponse {
|
||||||
|
string diff = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -458,6 +458,8 @@ messages!(
|
||||||
(GitChangeBranch, Background),
|
(GitChangeBranch, Background),
|
||||||
(CheckForPushedCommits, Background),
|
(CheckForPushedCommits, Background),
|
||||||
(CheckForPushedCommitsResponse, Background),
|
(CheckForPushedCommitsResponse, Background),
|
||||||
|
(GitDiff, Background),
|
||||||
|
(GitDiffResponse, Background),
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
@ -604,6 +606,7 @@ request_messages!(
|
||||||
(GitCreateBranch, Ack),
|
(GitCreateBranch, Ack),
|
||||||
(GitChangeBranch, Ack),
|
(GitChangeBranch, Ack),
|
||||||
(CheckForPushedCommits, CheckForPushedCommitsResponse),
|
(CheckForPushedCommits, CheckForPushedCommitsResponse),
|
||||||
|
(GitDiff, GitDiffResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
@ -709,6 +712,7 @@ entity_messages!(
|
||||||
GitChangeBranch,
|
GitChangeBranch,
|
||||||
GitCreateBranch,
|
GitCreateBranch,
|
||||||
CheckForPushedCommits,
|
CheckForPushedCommits,
|
||||||
|
GitDiff,
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue