1
0

auto-git:

[change] index.html
 [change] renderer.js
 [change] settings.html
 [change] src-tauri/src/main.rs
 [change] tauriBridge.js
This commit is contained in:
2026-07-12 12:19:00 +02:00
parent f047d61731
commit a967587181
5 changed files with 357 additions and 28 deletions

View File

@@ -310,6 +310,65 @@
</button> </button>
</div> </div>
<div id="rewordModeModal"
class="hidden fixed inset-0 z-50 items-center justify-center bg-black/40 px-4"
role="dialog" aria-modal="true" aria-labelledby="rewordModeTitle">
<div class="w-full max-w-md rounded-lg border bg-white p-5 shadow-xl"
style="border-color: var(--border)">
<h3 id="rewordModeTitle" class="text-lg font-semibold text-gray-900">
Reword commit message
</h3>
<p class="mt-2 text-sm text-gray-600">
Choose Automatic to ask the configured LLM, or Manual to type the message yourself.
</p>
<label class="mt-4 flex items-center gap-2 text-sm text-gray-700 select-none">
<input id="rememberRewordMode" type="checkbox" class="h-4 w-4" />
Remember my choice
</label>
<div class="mt-5 flex justify-end gap-2">
<button id="cancelRewordModeBtn" class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Cancel
</button>
<button id="manualRewordModeBtn" class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Manual
</button>
<button id="autoRewordModeBtn" class="px-3 py-2 text-sm border rounded font-semibold"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Automatic
</button>
</div>
</div>
</div>
<div id="manualRewordModal"
class="hidden fixed inset-0 z-50 items-center justify-center bg-black/40 px-4"
role="dialog" aria-modal="true" aria-labelledby="manualRewordTitle">
<form id="manualRewordForm"
class="w-full max-w-lg rounded-lg border bg-white p-5 shadow-xl"
style="border-color: var(--border)">
<h3 id="manualRewordTitle" class="text-lg font-semibold text-gray-900">
Enter commit message
</h3>
<p class="mt-2 text-sm text-gray-600">
Type the new single-line message for this commit.
</p>
<input id="manualRewordInput" type="text" required autocomplete="off"
class="mt-4 w-full rounded border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:outline-none focus:ring-2"
style="--tw-ring-color: var(--accent)" />
<div class="mt-5 flex justify-end gap-2">
<button id="cancelManualRewordBtn" type="button"
class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Cancel
</button>
<button id="applyManualRewordBtn" type="submit"
class="px-3 py-2 text-sm border rounded font-semibold"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Apply
</button>
</div>
</form>
</div>
<script src="renderer.js"></script> <script src="renderer.js"></script>
<script src="animeCat.js"></script> <script src="animeCat.js"></script>
<script type="module"> <script type="module">

View File

@@ -10,6 +10,15 @@ window.addEventListener('DOMContentLoaded', async () => {
const initRepoBtn = document.getElementById('initRepoBtn'); const initRepoBtn = document.getElementById('initRepoBtn');
const pushBtn = document.getElementById('pushBtn'); const pushBtn = document.getElementById('pushBtn');
const rewritePendingBtn = document.getElementById('rewritePendingBtn'); const rewritePendingBtn = document.getElementById('rewritePendingBtn');
const rewordModeModal = document.getElementById('rewordModeModal');
const rememberRewordMode = document.getElementById('rememberRewordMode');
const cancelRewordModeBtn = document.getElementById('cancelRewordModeBtn');
const manualRewordModeBtn = document.getElementById('manualRewordModeBtn');
const autoRewordModeBtn = document.getElementById('autoRewordModeBtn');
const manualRewordModal = document.getElementById('manualRewordModal');
const manualRewordForm = document.getElementById('manualRewordForm');
const manualRewordInput = document.getElementById('manualRewordInput');
const cancelManualRewordBtn = document.getElementById('cancelManualRewordBtn');
const panel = document.querySelector('.flex-1.p-4.overflow-y-auto'); const panel = document.querySelector('.flex-1.p-4.overflow-y-auto');
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
@@ -21,6 +30,59 @@ window.addEventListener('DOMContentLoaded', async () => {
let lastFolderPath = null; let lastFolderPath = null;
let lastPage = null; let lastPage = null;
function chooseRewordMode() {
rememberRewordMode.checked = false;
rewordModeModal.classList.remove('hidden');
rewordModeModal.classList.add('flex');
return new Promise(resolve => {
const finish = mode => {
rewordModeModal.classList.add('hidden');
rewordModeModal.classList.remove('flex');
resolve(mode ? { mode, remember: rememberRewordMode.checked } : null);
};
cancelRewordModeBtn.onclick = () => finish(null);
manualRewordModeBtn.onclick = () => finish('manual');
autoRewordModeBtn.onclick = () => finish('auto');
});
}
function promptForCommitMessage(currentMessage) {
manualRewordInput.value = currentMessage || '';
manualRewordInput.setCustomValidity('');
manualRewordModal.classList.remove('hidden');
manualRewordModal.classList.add('flex');
requestAnimationFrame(() => {
manualRewordInput.focus();
manualRewordInput.select();
});
return new Promise(resolve => {
const finish = message => {
manualRewordModal.classList.add('hidden');
manualRewordModal.classList.remove('flex');
resolve(message);
};
cancelManualRewordBtn.onclick = () => finish(null);
manualRewordInput.oninput = () => manualRewordInput.setCustomValidity('');
manualRewordInput.onkeydown = event => {
if (event.key === 'Escape') {
event.preventDefault();
finish(null);
}
};
manualRewordForm.onsubmit = event => {
event.preventDefault();
const message = manualRewordInput.value.trim();
if (!message) {
manualRewordInput.setCustomValidity('The commit message cannot be empty.');
manualRewordInput.reportValidity();
return;
}
manualRewordInput.setCustomValidity('');
finish(message);
};
});
}
const slot = document.getElementById('catSlot'); const slot = document.getElementById('catSlot');
window.cat = new window.AnimeCat(slot, { window.cat = new window.AnimeCat(slot, {
images: { images: {
@@ -771,10 +833,61 @@ window.addEventListener('DOMContentLoaded', async () => {
} }
} }
async function startTypedRewrite(hash, currentMessage, control) {
const message = await promptForCommitMessage(currentMessage);
if (message === null) return;
control.disabled = true;
try {
const result = await window.electronAPI.rewriteCommitWithMessage(
folderObj.path,
hash,
message
);
if (!result?.success) {
alert('Rewrite could not start:\n' + (result?.error || 'Unknown error'));
}
} catch (e) {
console.error('Typed commit rewrite error:', e);
alert('Rewrite could not start:\n' + (e.message || e));
} finally {
const refreshed = await getFolderObjByPath(folderObj.path);
if (refreshed) await renderContent(refreshed, currentPage);
}
}
async function handleRewordButton(commit, control) {
let mode = 'ask';
try {
mode = await window.settingsAPI.getRewordMode();
} catch (e) {
console.error('Could not load Reword choice:', e);
}
if (!['ask', 'auto', 'manual'].includes(mode)) mode = 'ask';
if (mode === 'ask') {
const choice = await chooseRewordMode();
if (!choice) return;
mode = choice.mode;
if (choice.remember) {
try {
await window.settingsAPI.setRewordMode(mode);
} catch (e) {
console.error('Could not remember Reword choice:', e);
alert('Your choice could not be saved, but this rewrite will continue.');
}
}
}
if (mode === 'manual') {
await startTypedRewrite(commit.hash, commit.message, control);
} else {
await startSingleRewrite(commit.hash, control);
}
}
// Reword is available for every commit in the current HEAD history. // Reword is available for every commit in the current HEAD history.
contentList.querySelectorAll('.reword-btn').forEach(btn => { contentList.querySelectorAll('.reword-btn').forEach(btn => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
await startSingleRewrite(btn.dataset.hash, btn); const commit = commits.find(item => item.hash === btn.dataset.hash);
if (commit) await handleRewordButton(commit, btn);
}); });
}); });

View File

@@ -176,6 +176,7 @@
const cbSkip = document.getElementById('skipPrompt'); const cbSkip = document.getElementById('skipPrompt');
const thresholdInput = document.getElementById('intelligentCommitThresholdInput'); const thresholdInput = document.getElementById('intelligentCommitThresholdInput');
const minutesInput = document.getElementById('intelligentCommitMinutesInput'); const minutesInput = document.getElementById('intelligentCommitMinutesInput');
const rewordModeSelect = document.getElementById('rewordModeSelect');
const ok = document.getElementById('okBtn'); const ok = document.getElementById('okBtn');
const cancel = document.getElementById('cancelBtn'); const cancel = document.getElementById('cancelBtn');
const cbAutostart = document.getElementById('autostart'); const cbAutostart = document.getElementById('autostart');
@@ -190,11 +191,12 @@
cbCloseToTray.checked = initialCloseToTray; cbCloseToTray.checked = initialCloseToTray;
// Initialwerte parallel laden // Initialwerte parallel laden
const [initialTheme, initialSkip, initialThreshold, initialMinutes] = await Promise.all([ const [initialTheme, initialSkip, initialThreshold, initialMinutes, initialRewordMode] = await Promise.all([
window.settingsAPI.getTheme(), window.settingsAPI.getTheme(),
window.settingsAPI.getSkipPrompt(), window.settingsAPI.getSkipPrompt(),
window.electronAPI.getIntelligentCommitThreshold(), window.electronAPI.getIntelligentCommitThreshold(),
window.electronAPI.getMinutesCommitThreshold() window.electronAPI.getMinutesCommitThreshold(),
window.settingsAPI.getRewordMode()
]); ]);
const fallbackTheme = VALID_THEMES.includes(initialTheme) ? initialTheme : 'sky'; const fallbackTheme = VALID_THEMES.includes(initialTheme) ? initialTheme : 'sky';
@@ -204,6 +206,9 @@
cbSkip.checked = initialSkip; cbSkip.checked = initialSkip;
thresholdInput.value = initialThreshold; thresholdInput.value = initialThreshold;
minutesInput.value = initialMinutes; minutesInput.value = initialMinutes;
rewordModeSelect.value = ['ask', 'auto', 'manual'].includes(initialRewordMode)
? initialRewordMode
: 'ask';
// **Vorschau**: Theme sofort übernehmen // **Vorschau**: Theme sofort übernehmen
themeSelect.addEventListener('change', async () => { themeSelect.addEventListener('change', async () => {
@@ -224,6 +229,7 @@
await window.settingsAPI.setAutostart(cbAutostart.checked); await window.settingsAPI.setAutostart(cbAutostart.checked);
await window.settingsAPI.setCloseToTray(cbCloseToTray.checked); await window.settingsAPI.setCloseToTray(cbCloseToTray.checked);
await window.settingsAPI.setTheme(themeSelect.value || fallbackTheme); await window.settingsAPI.setTheme(themeSelect.value || fallbackTheme);
await window.settingsAPI.setRewordMode(rewordModeSelect.value || 'ask');
// Threshold speichern (immer Wert aus Feld nehmen!) // Threshold speichern (immer Wert aus Feld nehmen!)
let threshold = parseInt(thresholdInput.value, 10); let threshold = parseInt(thresholdInput.value, 10);
@@ -343,6 +349,14 @@
<option value="grey">Grey</option> <option value="grey">Grey</option>
</select> </select>
</label> </label>
<label class="option" style="align-items:center;">
Reword button:
<select id="rewordModeSelect" style="margin-left:1em;">
<option value="ask">Ask every time</option>
<option value="auto">Automatic (LLM)</option>
<option value="manual">Manual input</option>
</select>
</label>
<label class="option"> <label class="option">
<input type="checkbox" id="skipPrompt" class="flex-none" /> <input type="checkbox" id="skipPrompt" class="flex-none" />
Skip prompt asking to remove .git folder if it has only one commit and no changes Skip prompt asking to remove .git folder if it has only one commit and no changes

View File

@@ -33,6 +33,7 @@ use tempfile::TempDir;
type CommandResult<T> = Result<T, String>; type CommandResult<T> = Result<T, String>;
const VALID_THEMES: &[&str] = &["sky", "default", "grey"]; const VALID_THEMES: &[&str] = &["sky", "default", "grey"];
const VALID_REWORD_MODES: &[&str] = &["ask", "auto", "manual"];
const EMPTY_TREE_HASH: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; const EMPTY_TREE_HASH: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
const SQUASH_CHUNK_WINDOW_MS: i64 = 2 * 60 * 1000; const SQUASH_CHUNK_WINDOW_MS: i64 = 2 * 60 * 1000;
const MAX_SQUASH_PROMPT_CHARS: usize = 25_000; const MAX_SQUASH_PROMPT_CHARS: usize = 25_000;
@@ -279,6 +280,8 @@ struct StoreData {
commit_model: Option<String>, commit_model: Option<String>,
#[serde(default)] #[serde(default)]
readme_model: Option<String>, readme_model: Option<String>,
#[serde(default = "default_reword_mode")]
reword_mode: String,
#[serde(default)] #[serde(default)]
author: Option<String>, author: Option<String>,
#[serde(default)] #[serde(default)]
@@ -305,6 +308,7 @@ impl Default for StoreData {
llm_buffer: Vec::new(), llm_buffer: Vec::new(),
commit_model: None, commit_model: None,
readme_model: None, readme_model: None,
reword_mode: default_reword_mode(),
author: None, author: None,
license: None, license: None,
} }
@@ -315,6 +319,10 @@ fn default_theme() -> String {
"sky".to_string() "sky".to_string()
} }
fn default_reword_mode() -> String {
"ask".to_string()
}
fn default_true() -> bool { fn default_true() -> bool {
true true
} }
@@ -340,6 +348,7 @@ struct CommitPage {
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CommitSummary { struct CommitSummary {
hash: String, hash: String,
date: String, date: String,
@@ -456,6 +465,9 @@ fn normalize_store(store: &mut StoreData) {
store.theme = if store.skymode { "sky" } else { "default" }.to_string(); store.theme = if store.skymode { "sky" } else { "default" }.to_string();
} }
store.skymode = store.theme == "sky"; store.skymode = store.theme == "sky";
if !VALID_REWORD_MODES.contains(&store.reword_mode.as_str()) {
store.reword_mode = default_reword_mode();
}
for folder in &mut store.folders { for folder in &mut store.folders {
let repo_exists = Path::new(&folder.path).join(".git").exists(); let repo_exists = Path::new(&folder.path).join(".git").exists();
@@ -2051,7 +2063,7 @@ fn open_settings_window(app: &AppHandle) -> CommandResult<()> {
let builder = let builder =
WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into())) WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into()))
.title("Einstellungen") .title("Einstellungen")
.inner_size(600.0, 500.0) .inner_size(600.0, 550.0)
.resizable(false) .resizable(false)
.background_color(ROSE_TITLEBAR_COLOR); .background_color(ROSE_TITLEBAR_COLOR);
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -2678,6 +2690,25 @@ fn set_readme_model(state: tauri::State<'_, AppState>, val: String) -> CommandRe
save_store(&state) save_store(&state)
} }
#[tauri::command]
fn get_reword_mode(state: tauri::State<'_, AppState>) -> CommandResult<String> {
Ok(state
.store
.lock()
.map_err(|e| e.to_string())?
.reword_mode
.clone())
}
#[tauri::command]
fn set_reword_mode(state: tauri::State<'_, AppState>, val: String) -> CommandResult<()> {
if !VALID_REWORD_MODES.contains(&val.as_str()) {
return Err("Invalid Reword button behavior.".to_string());
}
state.store.lock().map_err(|e| e.to_string())?.reword_mode = val;
save_store(&state)
}
#[tauri::command] #[tauri::command]
fn get_intelligent_commit_threshold(state: tauri::State<'_, AppState>) -> CommandResult<i64> { fn get_intelligent_commit_threshold(state: tauri::State<'_, AppState>) -> CommandResult<i64> {
Ok(state Ok(state
@@ -2892,6 +2923,7 @@ fn run_manual_rewrite_job(
folder_path: String, folder_path: String,
hashes: Vec<String>, hashes: Vec<String>,
scope: &'static str, scope: &'static str,
supplied_messages: Option<HashMap<String, String>>,
) { ) {
let state = app.state::<AppState>(); let state = app.state::<AppState>();
let total = hashes.len(); let total = hashes.len();
@@ -2948,40 +2980,58 @@ fn run_manual_rewrite_job(
}) })
.collect(); .collect();
let mut successful_messages = HashMap::new(); let mut successful_messages = supplied_messages.unwrap_or_default();
let mut llm_failures: Vec<(String, String)> = Vec::new(); let mut llm_failures: Vec<(String, String)> = Vec::new();
match ensure_ollama_running() { let uses_llm = successful_messages.is_empty();
match if uses_llm {
ensure_ollama_running()
} else {
Ok(())
} {
Ok(()) => { Ok(()) => {
for (index, hash) in hashes.iter().enumerate() { if !uses_llm {
emit_rewrite_progress( emit_rewrite_progress(
&app, &app,
&folder_path, &folder_path,
scope, scope,
"running", "running",
index,
total, total,
Some(hash), total,
hashes.first().map(String::as_str),
None, None,
); );
match generate_llm_message_for_commit(&app, &folder_path, hash, &model) { } else {
Ok(message) => { for (index, hash) in hashes.iter().enumerate() {
successful_messages.insert(hash.clone(), message); emit_rewrite_progress(
} &app,
Err(err) => { &folder_path,
eprintln!("[manualRewrite] {} failed: {err}", short_hash(hash)); scope,
llm_failures.push((hash.clone(), err)); "running",
index,
total,
Some(hash),
None,
);
match generate_llm_message_for_commit(&app, &folder_path, hash, &model) {
Ok(message) => {
successful_messages.insert(hash.clone(), message);
}
Err(err) => {
eprintln!("[manualRewrite] {} failed: {err}", short_hash(hash));
llm_failures.push((hash.clone(), err));
}
} }
emit_rewrite_progress(
&app,
&folder_path,
scope,
"running",
index + 1,
total,
Some(hash),
None,
);
} }
emit_rewrite_progress(
&app,
&folder_path,
scope,
"running",
index + 1,
total,
Some(hash),
None,
);
} }
} }
Err(err) => { Err(err) => {
@@ -3142,7 +3192,67 @@ fn rewrite_commit(
folder.rewrite_started_at = Some(now_ms()); folder.rewrite_started_at = Some(now_ms());
} }
save_store(&state)?; save_store(&state)?;
thread::spawn(move || run_manual_rewrite_job(app, folder_path, vec![full_hash], "single")); thread::spawn(move || {
run_manual_rewrite_job(app, folder_path, vec![full_hash], "single", None)
});
Ok(json!({ "success": true, "count": 1 }))
}
#[tauri::command]
fn rewrite_commit_with_message(
app: AppHandle,
state: tauri::State<'_, AppState>,
folder_path: String,
hash: String,
message: String,
) -> CommandResult<Value> {
let message = message.trim().to_string();
if message.is_empty() {
return Ok(json!({ "success": false, "error": "The commit message cannot be empty." }));
}
if message.lines().count() != 1 {
return Ok(
json!({ "success": false, "error": "Please enter a single-line commit message." }),
);
}
let full_hash = resolve_commit_hash(&folder_path, &hash)
.ok_or_else(|| format!("Commit {hash} was not found."))?;
if !is_commit_on_current_history(&folder_path, &full_hash) {
return Ok(json!({
"success": false,
"error": "This commit is not in the current HEAD history. Jump to its branch/history first."
}));
}
if is_rebase_in_progress(&folder_path) {
return Ok(json!({ "success": false, "error": "A Git rebase is already in progress." }));
}
{
let mut store = state.store.lock().map_err(|e| e.to_string())?;
let Some(folder) = store
.folders
.iter_mut()
.find(|folder| folder.path == folder_path)
else {
return Ok(json!({ "success": false, "error": "folder not found" }));
};
if folder.needs_relocation {
return Ok(json!({ "success": false, "error": "needs relocation" }));
}
if folder.rewrite_in_progress {
return Ok(json!({
"success": false,
"error": "Another rewrite is already running for this repository."
}));
}
folder.rewrite_in_progress = true;
folder.rewrite_started_at = Some(now_ms());
}
save_store(&state)?;
thread::spawn(move || {
let mut messages = HashMap::new();
messages.insert(full_hash.clone(), message);
run_manual_rewrite_job(app, folder_path, vec![full_hash], "typed", Some(messages));
});
Ok(json!({ "success": true, "count": 1 })) Ok(json!({ "success": true, "count": 1 }))
} }
@@ -3185,7 +3295,7 @@ fn rewrite_pending_commits(
}; };
let count = hashes.len(); let count = hashes.len();
save_store(&state)?; save_store(&state)?;
thread::spawn(move || run_manual_rewrite_job(app, folder_path, hashes, "pending")); thread::spawn(move || run_manual_rewrite_job(app, folder_path, hashes, "pending", None));
Ok(json!({ "success": true, "count": count })) Ok(json!({ "success": true, "count": count }))
} }
@@ -4164,6 +4274,8 @@ fn main() {
set_commit_model, set_commit_model,
get_readme_model, get_readme_model,
set_readme_model, set_readme_model,
get_reword_mode,
set_reword_mode,
get_intelligent_commit_threshold, get_intelligent_commit_threshold,
set_intelligent_commit_threshold, set_intelligent_commit_threshold,
get_minutes_commit_threshold, get_minutes_commit_threshold,
@@ -4182,6 +4294,7 @@ fn main() {
get_all_commit_hashes, get_all_commit_hashes,
trigger_rewrite_now, trigger_rewrite_now,
rewrite_commit, rewrite_commit,
rewrite_commit_with_message,
rewrite_pending_commits, rewrite_pending_commits,
show_folder_context_menu, show_folder_context_menu,
show_tree_context_menu, show_tree_context_menu,
@@ -4229,6 +4342,33 @@ mod tests {
assert_eq!(slugify_repository_name(""), "repository"); assert_eq!(slugify_repository_name(""), "repository");
} }
#[test]
fn reword_button_mode_defaults_to_ask_and_invalid_values_are_normalized() {
let mut store = StoreData::default();
assert_eq!(store.reword_mode, "ask");
store.reword_mode = "unexpected".to_string();
normalize_store(&mut store);
assert_eq!(store.reword_mode, "ask");
}
#[test]
fn commit_summary_uses_the_javascript_field_names() {
let summary = CommitSummary {
hash: "abcdef0".to_string(),
date: "2026-07-12T00:00:00Z".to_string(),
message: "Test message".to_string(),
needs_rewrite: true,
can_reword: true,
};
let serialized = serde_json::to_value(summary).unwrap();
assert_eq!(serialized["needsRewrite"], true);
assert_eq!(serialized["canReword"], true);
assert!(serialized.get("needs_rewrite").is_none());
assert!(serialized.get("can_reword").is_none());
}
#[test] #[test]
fn existing_project_ignore_rules_are_written_before_staging() { fn existing_project_ignore_rules_are_written_before_staging() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();

View File

@@ -64,6 +64,8 @@
setCommitModel: model => invoke('set_commit_model', { val: model }), setCommitModel: model => invoke('set_commit_model', { val: model }),
getReadmeModel: () => invoke('get_readme_model'), getReadmeModel: () => invoke('get_readme_model'),
setReadmeModel: model => invoke('set_readme_model', { val: model }), setReadmeModel: model => invoke('set_readme_model', { val: model }),
getRewordMode: () => invoke('get_reword_mode'),
setRewordMode: val => invoke('set_reword_mode', { val }),
close: () => invoke('close_settings'), close: () => invoke('close_settings'),
getAutostart: () => invoke('get_autostart'), getAutostart: () => invoke('get_autostart'),
setAutostart: val => invoke('set_autostart', { enabled: val }), setAutostart: val => invoke('set_autostart', { enabled: val }),
@@ -123,6 +125,7 @@
initRepo: folderPath => invoke('init_repo', { folderPath }), initRepo: folderPath => invoke('init_repo', { folderPath }),
triggerRewriteNow: folderPath => invoke('trigger_rewrite_now', { folderPath }), triggerRewriteNow: folderPath => invoke('trigger_rewrite_now', { folderPath }),
rewriteCommit: (folderPath, hash) => invoke('rewrite_commit', { folderPath, hash }), rewriteCommit: (folderPath, hash) => invoke('rewrite_commit', { folderPath, hash }),
rewriteCommitWithMessage: (folderPath, hash, message) => invoke('rewrite_commit_with_message', { folderPath, hash, message }),
rewritePendingCommits: folderPath => invoke('rewrite_pending_commits', { folderPath }) rewritePendingCommits: folderPath => invoke('rewrite_pending_commits', { folderPath })
}; };