mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-24 13:37:34 +03:00
Add working directory diff support and auto generate commit messages
This update enables the generation of a working directory diff and returns a Git commit message based on the diff changes. It includes changes to the API, Tauri, and frontend code. Notable modifications include: - Added `git_wd_diff` function to Tauri `src/main.rs` - Modified `Repository` struct, added `wd_diff` method to `src/repositories/repository.rs` - New `commit` function added to `src/lib/api.ts` - Added `fetchCommitMessage` function in `src/routes/projects/[projectId]/+page.svelte` - Minor UI adjustments in `src/routes/projects/[projectId]/player/+page.svelte` Overall, this commit improves user experience by automatically generating commit messages based on the changes made in the working directory.
This commit is contained in:
parent
4df03fbcdc
commit
1210d35288
@ -369,6 +369,13 @@ async fn git_status(
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn git_wd_diff(handle: tauri::AppHandle, project_id: &str) -> Result<String, Error> {
|
||||
let repo = repo_for_project(handle, project_id)?;
|
||||
let diff = repo.wd_diff().with_context(|| "Failed to get git diff")?;
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn git_file_paths(handle: tauri::AppHandle, project_id: &str) -> Result<Vec<String>, Error> {
|
||||
let repo = repo_for_project(handle, project_id)?;
|
||||
@ -579,7 +586,8 @@ fn main() {
|
||||
git_branches,
|
||||
git_branch,
|
||||
git_switch_branch,
|
||||
git_commit
|
||||
git_commit,
|
||||
git_wd_diff
|
||||
]);
|
||||
|
||||
let tauri_context = generate_context!();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{deltas, fs, projects, sessions, users};
|
||||
use anyhow::{Context, Result};
|
||||
use git2::{BranchType, Cred, Signature};
|
||||
use std::{collections::HashMap, env, path::Path};
|
||||
use std::{collections::HashMap, env, path::Path, str::from_utf8};
|
||||
use tauri::regex::Regex;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@ -172,6 +172,26 @@ impl Repository {
|
||||
Ok(branch.to_string())
|
||||
}
|
||||
|
||||
pub fn wd_diff(&self) -> Result<String> {
|
||||
println!("diffing");
|
||||
let repo = &self.git_repository;
|
||||
let head = repo.head()?;
|
||||
let tree = head.peel_to_tree()?;
|
||||
let diff = repo.diff_tree_to_workdir_with_index(Some(&tree), None)?;
|
||||
let mut buf = String::new();
|
||||
diff.print(git2::DiffFormat::Patch, |delta, hunk, line| {
|
||||
buf.push_str(&format!(
|
||||
"{:?} {}",
|
||||
delta.status(),
|
||||
delta.new_file().path().unwrap().to_str().unwrap()
|
||||
));
|
||||
buf.push_str(from_utf8(line.content()).unwrap());
|
||||
buf.push_str("\n");
|
||||
true
|
||||
})?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn switch_branch(&self, branch_name: &str) -> Result<bool> {
|
||||
self.flush_session(&None)
|
||||
.with_context(|| "failed to flush session before switching branch")?;
|
||||
|
@ -128,6 +128,17 @@ export default (
|
||||
}).then(parseResponseJSON);
|
||||
}
|
||||
},
|
||||
summarize: {
|
||||
commit: (token: string, params: { diff: string; uid?: string }): Promise<string> =>
|
||||
fetch(getUrl('summarize/commit.json'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(parseResponseJSON)
|
||||
},
|
||||
projects: {
|
||||
create: (token: string, params: { name: string; uid?: string }): Promise<Project> =>
|
||||
fetch(getUrl('projects.json'), {
|
||||
|
@ -37,8 +37,20 @@ export const load: LayoutLoad = async ({ parent, params }) => {
|
||||
});
|
||||
return activitySorted.slice(0, 20);
|
||||
});
|
||||
const user = building
|
||||
? {
|
||||
...readable<undefined>(undefined),
|
||||
set: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
delete: () => {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
: await (await import('$lib/users')).default();
|
||||
|
||||
return {
|
||||
user: user,
|
||||
project: projects.get(params.projectId),
|
||||
projectId: params.projectId,
|
||||
orderedSessionsFromLastFourDays: orderedSessionsFromLastFourDays,
|
||||
|
@ -13,14 +13,18 @@
|
||||
import { navigating } from '$app/stores';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { goto } from '$app/navigation';
|
||||
import Api from '$lib/api';
|
||||
|
||||
const api = Api({ fetch });
|
||||
const getBranch = (params: { projectId: string }) => invoke<string>('git_branch', params);
|
||||
const getDiff = (params: { projectId: string }) => invoke<string>('git_wd_diff', params);
|
||||
|
||||
export let data: LayoutData;
|
||||
$: project = data.project;
|
||||
$: filesStatus = data.filesStatus;
|
||||
$: recentActivity = data.recentActivity as Readable<Activity[]>;
|
||||
$: recentActivity = data.recentActivity;
|
||||
$: orderedSessionsFromLastFourDays = data.orderedSessionsFromLastFourDays;
|
||||
$: user = data.user;
|
||||
|
||||
const commit = (params: {
|
||||
projectId: string;
|
||||
@ -31,6 +35,7 @@
|
||||
|
||||
let latestDeltasByDateByFile: Record<number, Record<string, Delta[][]>[]> = {};
|
||||
let commitMessage: string;
|
||||
let placeholderMessage = 'Description of changes';
|
||||
let initiatedCommit = false;
|
||||
let filesSelectedForCommit: string[] = [];
|
||||
|
||||
@ -183,6 +188,27 @@
|
||||
return sessionsByFile;
|
||||
}
|
||||
|
||||
function fetchCommitMessage() {
|
||||
if ($project && $user) {
|
||||
placeholderMessage = 'Summarizing changes...';
|
||||
console.log('FETCHING DIFF');
|
||||
getDiff({
|
||||
projectId: $project.id
|
||||
}).then((result) => {
|
||||
console.log('DIFF', result);
|
||||
api.summarize
|
||||
.commit($user?.access_token, {
|
||||
diff: result,
|
||||
uid: $project.id
|
||||
})
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
commitMessage = result.message;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// order the sessions and summarize the changes by file
|
||||
function orderedSessions(dateSessions: Record<number, Record<string, Delta[][]>[]>) {
|
||||
return Object.entries(dateSessions)
|
||||
@ -288,7 +314,7 @@
|
||||
<div class="truncate pl-2 font-mono text-zinc-300">
|
||||
{toHumanBranchName(gitBranch)}
|
||||
</div>
|
||||
<div class="carrot flex items-center pl-3">
|
||||
<div class="carrot flex hidden items-center pl-3">
|
||||
<svg width="7" height="5" viewBox="0 0 7 5" fill="none" class="fill-zinc-400">
|
||||
<path
|
||||
d="M3.87796 4.56356C3.67858 4.79379 3.32142 4.79379 3.12204 4.56356L0.319371 1.32733C0.0389327 1.00351 0.268959 0.5 0.697336 0.5L6.30267 0.500001C6.73104 0.500001 6.96107 1.00351 6.68063 1.32733L3.87796 4.56356Z"
|
||||
@ -297,7 +323,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="branch-count-container text-md hover:text-blue-500 ">6 branches</div>
|
||||
<div class="branch-count-container text-md hidden hover:text-blue-500 ">6 branches</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $filesStatus.length == 0}
|
||||
@ -341,7 +367,7 @@
|
||||
<textarea
|
||||
rows="4"
|
||||
name="message"
|
||||
placeholder="Description of changes"
|
||||
placeholder={placeholderMessage}
|
||||
bind:value={commitMessage}
|
||||
class="mb-2 block w-full rounded-md border-0 p-4 text-zinc-200 ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:py-1.5 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
@ -384,7 +410,7 @@
|
||||
filesSelectedForCommit = $filesStatus.map((file) => {
|
||||
return file.path;
|
||||
});
|
||||
|
||||
fetchCommitMessage();
|
||||
initiatedCommit = true;
|
||||
}}>Commit changes</button
|
||||
>
|
||||
|
20
src/routes/projects/[projectId]/+page.ts
Normal file
20
src/routes/projects/[projectId]/+page.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { building } from '$app/environment';
|
||||
import { readable } from 'svelte/store';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const user = building
|
||||
? {
|
||||
...readable<undefined>(undefined),
|
||||
set: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
delete: () => {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
: await (await import('$lib/users')).default();
|
||||
return {
|
||||
user
|
||||
};
|
||||
};
|
@ -544,15 +544,17 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="playback-controller-ui mx-auto flex w-full items-center gap-2 justify-between">
|
||||
<div
|
||||
class="playback-controller-ui mx-auto flex w-full items-center justify-between gap-2"
|
||||
>
|
||||
<div class="left-side flex space-x-8">
|
||||
<div class="play-button-button-container">
|
||||
{#if interval}
|
||||
<button on:click={stop}
|
||||
><IconPlayerPauseFilled
|
||||
class="playback-button-play icon-pointer h-6 w-6"
|
||||
/></button
|
||||
>
|
||||
<button on:click={stop}
|
||||
><IconPlayerPauseFilled
|
||||
class="playback-button-play icon-pointer h-6 w-6"
|
||||
/></button
|
||||
>
|
||||
{:else}
|
||||
<button on:click={play}
|
||||
><IconPlayerPlayFilled class="icon-pointer h-6 w-6" /></button
|
||||
@ -560,8 +562,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="back-forward-button-container ">
|
||||
<button on:click={decrementPlayerValue} class="playback-button-back group">
|
||||
<svg
|
||||
@ -602,11 +602,10 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button on:click={speedUp}>{speed}x</button>
|
||||
</div>
|
||||
|
||||
<div class="align-center flex gap-2 flex-row-reverse">
|
||||
<div class="align-center flex flex-row-reverse gap-2">
|
||||
<button class="checkbox-button ">
|
||||
<label
|
||||
for="full-context-checkbox"
|
||||
@ -645,10 +644,10 @@
|
||||
</label>
|
||||
</button>
|
||||
{#if !fullContext}
|
||||
<input
|
||||
type="number"
|
||||
bind:value={context}
|
||||
class="pl-2 pr-1 py-1 rounded w-14"
|
||||
<input
|
||||
type="number"
|
||||
bind:value={context}
|
||||
class="w-14 rounded py-1 pl-2 pr-1"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user