mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 18:49:11 +03:00
commit: make commit work
This commit is contained in:
parent
b9265ccdef
commit
10da421c10
@ -17,7 +17,7 @@ use anyhow::{Context, Result};
|
|||||||
use deltas::Delta;
|
use deltas::Delta;
|
||||||
use git::activity;
|
use git::activity;
|
||||||
use serde::{ser::SerializeMap, Serialize};
|
use serde::{ser::SerializeMap, Serialize};
|
||||||
use std::{collections::HashMap, ops::Range, sync::Mutex};
|
use std::{collections::HashMap, ops::Range, path::Path, sync::Mutex};
|
||||||
use storage::Storage;
|
use storage::Storage;
|
||||||
use tauri::{generate_context, Manager};
|
use tauri::{generate_context, Manager};
|
||||||
use tauri_plugin_log::{
|
use tauri_plugin_log::{
|
||||||
@ -394,7 +394,7 @@ async fn git_activity(
|
|||||||
async fn git_status(
|
async fn git_status(
|
||||||
handle: tauri::AppHandle,
|
handle: tauri::AppHandle,
|
||||||
project_id: &str,
|
project_id: &str,
|
||||||
) -> Result<HashMap<String, repositories::FileStatus>, Error> {
|
) -> Result<HashMap<String, (repositories::FileStatus, bool)>, Error> {
|
||||||
let repo = repo_for_project(handle, project_id)?;
|
let repo = repo_for_project(handle, project_id)?;
|
||||||
let files = repo.status().with_context(|| "Failed to get git status")?;
|
let files = repo.status().with_context(|| "Failed to get git status")?;
|
||||||
Ok(files)
|
Ok(files)
|
||||||
@ -478,21 +478,45 @@ async fn git_switch_branch(
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[timed(duration(printer = "debug!"))]
|
||||||
|
#[tauri::command(async)]
|
||||||
|
async fn git_stage(
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
project_id: &str,
|
||||||
|
paths: Vec<&str>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let repo = repo_for_project(handle, project_id)?;
|
||||||
|
repo.stage_files(paths.iter().map(|p| Path::new(p)).collect())
|
||||||
|
.with_context(|| "failed to stage file")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[timed(duration(printer = "debug!"))]
|
||||||
|
#[tauri::command(async)]
|
||||||
|
async fn git_unstage(
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
project_id: &str,
|
||||||
|
paths: Vec<&str>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let repo = repo_for_project(handle, project_id)?;
|
||||||
|
repo.unstage_files(paths.iter().map(|p| Path::new(p)).collect())
|
||||||
|
.with_context(|| "failed to unstage file")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[timed(duration(printer = "debug!"))]
|
#[timed(duration(printer = "debug!"))]
|
||||||
#[tauri::command(async)]
|
#[tauri::command(async)]
|
||||||
async fn git_commit(
|
async fn git_commit(
|
||||||
handle: tauri::AppHandle,
|
handle: tauri::AppHandle,
|
||||||
project_id: &str,
|
project_id: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
files: Vec<&str>,
|
|
||||||
push: bool,
|
push: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let repo = repo_for_project(handle, project_id)?;
|
let repo = repo_for_project(handle, project_id)?;
|
||||||
let success = repo
|
repo.commit(message, push)
|
||||||
.commit(message, files, push)
|
|
||||||
.with_context(|| "Failed to commit")?;
|
.with_context(|| "Failed to commit")?;
|
||||||
|
|
||||||
Ok(success)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -619,6 +643,8 @@ fn main() {
|
|||||||
git_head,
|
git_head,
|
||||||
git_switch_branch,
|
git_switch_branch,
|
||||||
git_commit,
|
git_commit,
|
||||||
|
git_stage,
|
||||||
|
git_unstage,
|
||||||
git_wd_diff,
|
git_wd_diff,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use std::{
|
|||||||
use tauri::regex::Regex;
|
use tauri::regex::Regex;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Copy, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum FileStatus {
|
pub enum FileStatus {
|
||||||
Added,
|
Added,
|
||||||
@ -22,6 +22,24 @@ pub enum FileStatus {
|
|||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<git2::Status> for FileStatus {
|
||||||
|
fn from(status: git2::Status) -> Self {
|
||||||
|
if status.is_index_new() || status.is_wt_new() {
|
||||||
|
FileStatus::Added
|
||||||
|
} else if status.is_index_modified() || status.is_wt_modified() {
|
||||||
|
FileStatus::Modified
|
||||||
|
} else if status.is_index_deleted() || status.is_wt_deleted() {
|
||||||
|
FileStatus::Deleted
|
||||||
|
} else if status.is_index_renamed() || status.is_wt_renamed() {
|
||||||
|
FileStatus::Renamed
|
||||||
|
} else if status.is_index_typechange() || status.is_wt_typechange() {
|
||||||
|
FileStatus::TypeChange
|
||||||
|
} else {
|
||||||
|
FileStatus::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Repository {
|
pub struct Repository {
|
||||||
pub project: projects::Project,
|
pub project: projects::Project,
|
||||||
@ -237,12 +255,13 @@ impl Repository {
|
|||||||
Ok(activity)
|
Ok(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get file status from git
|
// returns statuses of the unstaged files in the repository
|
||||||
pub fn status(&self) -> Result<HashMap<String, FileStatus>> {
|
fn unstaged_statuses(&self) -> Result<HashMap<String, FileStatus>> {
|
||||||
let mut options = git2::StatusOptions::new();
|
let mut options = git2::StatusOptions::new();
|
||||||
options.include_untracked(true);
|
options.include_untracked(true);
|
||||||
options.include_ignored(false);
|
|
||||||
options.recurse_untracked_dirs(true);
|
options.recurse_untracked_dirs(true);
|
||||||
|
options.include_ignored(false);
|
||||||
|
options.show(git2::StatusShow::Workdir);
|
||||||
|
|
||||||
let git_repository = self.git_repository.lock().unwrap();
|
let git_repository = self.git_repository.lock().unwrap();
|
||||||
// get the status of the repository
|
// get the status of the repository
|
||||||
@ -250,34 +269,58 @@ impl Repository {
|
|||||||
.statuses(Some(&mut options))
|
.statuses(Some(&mut options))
|
||||||
.with_context(|| "failed to get repository status")?;
|
.with_context(|| "failed to get repository status")?;
|
||||||
|
|
||||||
let mut files = HashMap::new();
|
let files = statuses
|
||||||
|
.iter()
|
||||||
// iterate over the statuses
|
.map(|entry| {
|
||||||
for entry in statuses.iter() {
|
|
||||||
// get the path of the entry
|
|
||||||
let path = entry.path().unwrap();
|
let path = entry.path().unwrap();
|
||||||
// get the status as a string
|
(path.to_string(), FileStatus::from(entry.status()))
|
||||||
let istatus = match entry.status() {
|
})
|
||||||
git2::Status::WT_NEW => FileStatus::Added,
|
.collect();
|
||||||
git2::Status::WT_MODIFIED => FileStatus::Modified,
|
|
||||||
git2::Status::WT_DELETED => FileStatus::Deleted,
|
|
||||||
git2::Status::WT_RENAMED => FileStatus::Renamed,
|
|
||||||
git2::Status::WT_TYPECHANGE => FileStatus::TypeChange,
|
|
||||||
git2::Status::INDEX_NEW => FileStatus::Added,
|
|
||||||
git2::Status::INDEX_MODIFIED => FileStatus::Modified,
|
|
||||||
git2::Status::INDEX_DELETED => FileStatus::Deleted,
|
|
||||||
git2::Status::INDEX_RENAMED => FileStatus::Renamed,
|
|
||||||
git2::Status::INDEX_TYPECHANGE => FileStatus::TypeChange,
|
|
||||||
_ => FileStatus::Other,
|
|
||||||
};
|
|
||||||
files.insert(path.to_string(), istatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(files);
|
return Ok(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns statuses of the staged files in the repository
|
||||||
|
fn staged_statuses(&self) -> Result<HashMap<String, FileStatus>> {
|
||||||
|
let mut options = git2::StatusOptions::new();
|
||||||
|
options.include_untracked(true);
|
||||||
|
options.include_ignored(false);
|
||||||
|
options.recurse_untracked_dirs(true);
|
||||||
|
options.show(git2::StatusShow::Index);
|
||||||
|
|
||||||
|
let git_repository = self.git_repository.lock().unwrap();
|
||||||
|
// get the status of the repository
|
||||||
|
let statuses = git_repository
|
||||||
|
.statuses(Some(&mut options))
|
||||||
|
.with_context(|| "failed to get repository status")?;
|
||||||
|
|
||||||
|
let files = statuses
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
|
let path = entry.path().unwrap();
|
||||||
|
(path.to_string(), FileStatus::from(entry.status()))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Ok(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get file status from git
|
||||||
|
pub fn status(&self) -> Result<HashMap<String, (FileStatus, bool)>> {
|
||||||
|
let staged_statuses = self.staged_statuses()?;
|
||||||
|
let unstaged_statuses = self.unstaged_statuses()?;
|
||||||
|
let mut statuses = HashMap::new();
|
||||||
|
unstaged_statuses.iter().for_each(|(path, status)| {
|
||||||
|
statuses.insert(path.clone(), (*status, false));
|
||||||
|
});
|
||||||
|
staged_statuses.iter().for_each(|(path, status)| {
|
||||||
|
statuses.insert(path.clone(), (*status, true));
|
||||||
|
});
|
||||||
|
Ok(statuses)
|
||||||
|
}
|
||||||
|
|
||||||
// commit method
|
// commit method
|
||||||
pub fn commit(&self, message: &str, files: Vec<&str>, push: bool) -> Result<()> {
|
pub fn commit(&self, message: &str, push: bool) -> Result<()> {
|
||||||
let repo = self.git_repository.lock().unwrap();
|
let repo = self.git_repository.lock().unwrap();
|
||||||
|
|
||||||
let config = repo.config().with_context(|| "failed to get config")?;
|
let config = repo.config().with_context(|| "failed to get config")?;
|
||||||
@ -288,25 +331,11 @@ impl Repository {
|
|||||||
.get_string("user.email")
|
.get_string("user.email")
|
||||||
.with_context(|| "failed to get user.email")?;
|
.with_context(|| "failed to get user.email")?;
|
||||||
|
|
||||||
// Get the repository's index
|
|
||||||
let mut index = repo.index()?;
|
|
||||||
|
|
||||||
// Add the specified files to the index
|
|
||||||
for path_str in files {
|
|
||||||
let path = Path::new(path_str);
|
|
||||||
index
|
|
||||||
.add_path(path)
|
|
||||||
.with_context(|| format!("failed to add path {} to index", path_str.to_string()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the updated index to disk
|
|
||||||
index.write().with_context(|| "failed to write index")?;
|
|
||||||
|
|
||||||
// Get the default signature for the repository
|
// Get the default signature for the repository
|
||||||
let signature = Signature::now(&name, &email).with_context(|| "failed to get signature")?;
|
let signature = Signature::now(&name, &email).with_context(|| "failed to get signature")?;
|
||||||
|
|
||||||
// Create the commit with the updated index
|
// Create the commit with current index
|
||||||
let tree_id = index.write_tree()?;
|
let tree_id = repo.index()?.write_tree()?;
|
||||||
let tree = repo.find_tree(tree_id)?;
|
let tree = repo.find_tree(tree_id)?;
|
||||||
let parent_commit = repo.head()?.peel_to_commit()?;
|
let parent_commit = repo.head()?.peel_to_commit()?;
|
||||||
let commit = repo.commit(
|
let commit = repo.commit(
|
||||||
@ -385,6 +414,55 @@ impl Repository {
|
|||||||
.with_context(|| format!("{}: failed to flush session", &self.project.id))?;
|
.with_context(|| format!("{}: failed to flush session", &self.project.id))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stage_files(&self, paths: Vec<&Path>) -> Result<()> {
|
||||||
|
let repo = self.git_repository.lock().unwrap();
|
||||||
|
let mut index = repo.index()?;
|
||||||
|
for path in paths {
|
||||||
|
// to "stage" a file means to:
|
||||||
|
// - remove it from the index if file is deleted
|
||||||
|
// - overwrite it in the index otherwise
|
||||||
|
if !Path::new(&self.project.path).join(path).exists() {
|
||||||
|
index.remove_path(path).with_context(|| {
|
||||||
|
format!("failed to remove path {} from index", path.display())
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
index
|
||||||
|
.add_path(path)
|
||||||
|
.with_context(|| format!("failed to add path {} to index", path.display()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index.write().with_context(|| "failed to write index")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unstage_files(&self, paths: Vec<&Path>) -> Result<()> {
|
||||||
|
let repo = self.git_repository.lock().unwrap();
|
||||||
|
let head_tree = repo.head()?.peel_to_tree()?;
|
||||||
|
let mut head_index = git2::Index::new()?;
|
||||||
|
head_index.read_tree(&head_tree)?;
|
||||||
|
let mut index = repo.index()?;
|
||||||
|
for path in paths {
|
||||||
|
// to "unstage" a file means to:
|
||||||
|
// - put head version of the file in the index if it exists
|
||||||
|
// - remove it from the index otherwise
|
||||||
|
let head_index_entry = head_index.iter().find(|entry| {
|
||||||
|
let entry_path = String::from_utf8(entry.path.clone());
|
||||||
|
entry_path.as_ref().unwrap() == path.to_str().unwrap()
|
||||||
|
});
|
||||||
|
if let Some(entry) = head_index_entry {
|
||||||
|
index
|
||||||
|
.add(&entry)
|
||||||
|
.with_context(|| format!("failed to add path {} to index", path.display()))?;
|
||||||
|
} else {
|
||||||
|
index.remove_path(path).with_context(|| {
|
||||||
|
format!("failed to remove path {} from index", path.display())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index.write().with_context(|| "failed to write index")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
|
@ -9,3 +9,9 @@ export const commit = (params: {
|
|||||||
files: Array<string>;
|
files: Array<string>;
|
||||||
push: boolean;
|
push: boolean;
|
||||||
}) => invoke<boolean>('git_commit', params);
|
}) => invoke<boolean>('git_commit', params);
|
||||||
|
|
||||||
|
export const stage = (params: { projectId: string; paths: Array<string> }) =>
|
||||||
|
invoke<void>('git_stage', params);
|
||||||
|
|
||||||
|
export const unstage = (params: { projectId: string; paths: Array<string> }) =>
|
||||||
|
invoke<void>('git_unstage', params);
|
||||||
|
@ -1,36 +1,37 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { appWindow } from '@tauri-apps/api/window';
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import { writable, type Readable } from 'svelte/store';
|
import { writable, type Readable } from 'svelte/store';
|
||||||
import { log } from '$lib';
|
|
||||||
|
|
||||||
export type Status = {
|
export type Status = {
|
||||||
path: string;
|
path: string;
|
||||||
status: FileStatus;
|
status: FileStatus;
|
||||||
|
staged: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'typeChange' | 'other';
|
type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'typeChange' | 'other';
|
||||||
|
|
||||||
const list = (params: { projectId: string }) =>
|
const list = (params: { projectId: string }) =>
|
||||||
invoke<Record<string, FileStatus>>('git_status', params);
|
invoke<Record<string, [FileStatus, boolean]>>('git_status', params);
|
||||||
|
|
||||||
const convertToStatuses = (statusesGit: Record<string, FileStatus>): Status[] =>
|
const convertToStatuses = (statusesGit: Record<string, [FileStatus, boolean]>): Status[] =>
|
||||||
Object.entries(statusesGit).map((status) => ({
|
Object.entries(statusesGit).map((status) => ({
|
||||||
path: status[0],
|
path: status[0],
|
||||||
status: status[1]
|
status: status[1][0],
|
||||||
|
staged: status[1][1]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default async (params: { projectId: string }) => {
|
export default async (params: { projectId: string }) => {
|
||||||
const statuses = await list(params).then(convertToStatuses);
|
const statuses = await list(params).then(convertToStatuses);
|
||||||
const store = writable(statuses);
|
const store = writable(statuses);
|
||||||
|
|
||||||
appWindow.listen(`project://${params.projectId}/git/index`, async () => {
|
[
|
||||||
log.info(`Status: Received git index event, projectId: ${params.projectId}`);
|
`project://${params.projectId}/git/index`,
|
||||||
|
`project://${params.projectId}/git/activity`,
|
||||||
|
`project://${params.projectId}/sessions`
|
||||||
|
].forEach((eventName) => {
|
||||||
|
appWindow.listen(eventName, async () => {
|
||||||
store.set(await list(params).then(convertToStatuses));
|
store.set(await list(params).then(convertToStatuses));
|
||||||
});
|
});
|
||||||
|
|
||||||
appWindow.listen(`project://${params.projectId}/sessions`, async () => {
|
|
||||||
log.info(`Status: Received sessions event, projectId: ${params.projectId}`);
|
|
||||||
store.set(await list(params).then(convertToStatuses));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return store as Readable<Status[]>;
|
return store as Readable<Status[]>;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { collapsable } from '$lib/paths';
|
import { collapsable } from '$lib/paths';
|
||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
import { commit } from '$lib/git';
|
import * as git from '$lib/git';
|
||||||
import DiffViewer from '$lib/components/DiffViewer.svelte';
|
import DiffViewer from '$lib/components/DiffViewer.svelte';
|
||||||
import { error, success } from '$lib/toasts';
|
import { error, success } from '$lib/toasts';
|
||||||
import { IconRotateClockwise } from '$lib/components/icons';
|
import { IconRotateClockwise } from '$lib/components/icons';
|
||||||
@ -13,7 +13,6 @@
|
|||||||
let summary = '';
|
let summary = '';
|
||||||
let description = '';
|
let description = '';
|
||||||
|
|
||||||
let selectedFiles = $statuses.map(({ path }) => path);
|
|
||||||
const selectedDiffPath = writable($statuses.at(0)?.path);
|
const selectedDiffPath = writable($statuses.at(0)?.path);
|
||||||
const selectedDiff = derived(
|
const selectedDiff = derived(
|
||||||
[diffs, selectedDiffPath],
|
[diffs, selectedDiffPath],
|
||||||
@ -36,14 +35,15 @@
|
|||||||
const paths = formData.getAll('path') as string[];
|
const paths = formData.getAll('path') as string[];
|
||||||
|
|
||||||
isCommitting = true;
|
isCommitting = true;
|
||||||
commit({
|
git
|
||||||
|
.commit({
|
||||||
projectId,
|
projectId,
|
||||||
message: description.length > 0 ? `${summary}\n\n${description}` : summary,
|
message: description.length > 0 ? `${summary}\n\n${description}` : summary,
|
||||||
files: paths,
|
files: paths,
|
||||||
push: false
|
push: false
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
success('Commit successfull!');
|
success('Commit created');
|
||||||
reset();
|
reset();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -58,7 +58,9 @@
|
|||||||
if ($user === undefined) return;
|
if ($user === undefined) return;
|
||||||
|
|
||||||
const partialDiff = Object.fromEntries(
|
const partialDiff = Object.fromEntries(
|
||||||
Object.entries($diffs).filter(([key]) => selectedFiles.includes(key))
|
Object.entries($diffs).filter(([key]) =>
|
||||||
|
$statuses.some((status) => status.path === key && status.staged)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const diff = Object.values(partialDiff).join('\n').slice(0, 5000);
|
const diff = Object.values(partialDiff).join('\n').slice(0, 5000);
|
||||||
|
|
||||||
@ -86,13 +88,27 @@
|
|||||||
const onGroupCheckboxClick = (e: Event) => {
|
const onGroupCheckboxClick = (e: Event) => {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
if (target.checked) {
|
if (target.checked) {
|
||||||
selectedFiles = $statuses.map(({ path }) => path);
|
git
|
||||||
|
.stage({
|
||||||
|
projectId,
|
||||||
|
paths: $statuses.filter(({ staged }) => !staged).map(({ path }) => path)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
error('Failed to stage files');
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
selectedFiles = [];
|
git
|
||||||
|
.unstage({
|
||||||
|
projectId,
|
||||||
|
paths: $statuses.filter(({ staged }) => staged).map(({ path }) => path)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
error('Failed to unstage files');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$: isEnabled = summary.length > 0 && selectedFiles.length > 0;
|
$: isCommitEnabled = summary.length > 0 && $statuses.filter(({ staged }) => staged).length > 0;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="commit-page" class="flex h-full w-full gap-2 p-2">
|
<div id="commit-page" class="flex h-full w-full gap-2 p-2">
|
||||||
@ -106,8 +122,10 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="cursor-pointer disabled:opacity-50"
|
class="cursor-pointer disabled:opacity-50"
|
||||||
on:click={onGroupCheckboxClick}
|
on:click={onGroupCheckboxClick}
|
||||||
checked={$statuses.length === selectedFiles.length}
|
checked={$statuses.every(({ staged }) => staged)}
|
||||||
indeterminate={$statuses.length > selectedFiles.length && selectedFiles.length > 0}
|
indeterminate={$statuses.some(({ staged }) => staged) &&
|
||||||
|
$statuses.some(({ staged }) => !staged) &&
|
||||||
|
$statuses.length > 0}
|
||||||
disabled={isCommitting || isGeneratingCommitMessage}
|
disabled={isCommitting || isGeneratingCommitMessage}
|
||||||
/>
|
/>
|
||||||
<h1 class="m-auto flex">
|
<h1 class="m-auto flex">
|
||||||
@ -115,7 +133,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#each $statuses as { path }, i}
|
{#each $statuses as { path, staged }, i}
|
||||||
<li
|
<li
|
||||||
class:bg-gb-700={$selectedDiffPath === path}
|
class:bg-gb-700={$selectedDiffPath === path}
|
||||||
class:hover:bg-divider={$selectedDiffPath !== path}
|
class:hover:bg-divider={$selectedDiffPath !== path}
|
||||||
@ -125,9 +143,18 @@
|
|||||||
<input
|
<input
|
||||||
class="ml-4 cursor-pointer py-2 disabled:opacity-50"
|
class="ml-4 cursor-pointer py-2 disabled:opacity-50"
|
||||||
disabled={isCommitting || isGeneratingCommitMessage}
|
disabled={isCommitting || isGeneratingCommitMessage}
|
||||||
|
on:click|preventDefault={() => {
|
||||||
|
staged
|
||||||
|
? git.unstage({ projectId, paths: [path] }).catch(() => {
|
||||||
|
error('Failed to unstage file');
|
||||||
|
})
|
||||||
|
: git.stage({ projectId, paths: [path] }).catch(() => {
|
||||||
|
error('Failed to stage file');
|
||||||
|
});
|
||||||
|
}}
|
||||||
name="path"
|
name="path"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:group={selectedFiles}
|
checked={staged}
|
||||||
value={path}
|
value={path}
|
||||||
/>
|
/>
|
||||||
<label class="flex w-full overflow-auto" for="path">
|
<label class="flex w-full overflow-auto" for="path">
|
||||||
@ -172,7 +199,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
disabled={!isEnabled || isGeneratingCommitMessage}
|
disabled={!isCommitEnabled || isGeneratingCommitMessage}
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded bg-[#2563EB] py-2 px-4 text-lg disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded bg-[#2563EB] py-2 px-4 text-lg disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
Loading…
Reference in New Issue
Block a user