feat(branches): add bulk delete for branch folders
Add a context-menu action to delete all branches in a folder at once. Top-level remote folders are protected and the currently checked-out branch is preserved. The BranchPanel API now includes folder depth to prevent accidental bulk-deletes and summarizes individual failures after processing. UI polish and localization were applied, and the package/version metadata, changelog, and help overlay were updated for the 2026.8.4 release. - Bulk delete with top-level remote protection and failure summaries. - BranchPanel now tracks folder depth and disables unsafe context menus. - Version bump, changelog/help overlay updates, and UI/localization tweaks.
This commit is contained in:
@@ -4,6 +4,25 @@ All notable user-facing changes to Gitty are documented in this file.
|
|||||||
|
|
||||||
The project uses calendar-style versions in the form `YYYY.M.PATCH`.
|
The project uses calendar-style versions in the form `YYYY.M.PATCH`.
|
||||||
|
|
||||||
|
## [2026.8.4] - 2026-08-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Branch folders now have a context-menu action for deleting all contained
|
||||||
|
local or remote branches at once. The top-level remote folder such as
|
||||||
|
`origin` is protected, while its nested folders remain manageable. The
|
||||||
|
currently checked-out branch is kept,
|
||||||
|
and individual failures are reported after the remaining branches have been
|
||||||
|
processed.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The Compare selector is fully localized in German and uses the same visual
|
||||||
|
language as the external-tool selectors for its fields, groups, typography,
|
||||||
|
and dialog surfaces.
|
||||||
|
- Repository-tab close buttons are square and have clearer spacing, hover
|
||||||
|
behavior, and keyboard-focus feedback.
|
||||||
|
|
||||||
## [2026.8.3] - 2026-08-13
|
## [2026.8.3] - 2026-08-13
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -161,6 +180,7 @@ The project uses calendar-style versions in the form `YYYY.M.PATCH`.
|
|||||||
[2026.07.22]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.22
|
[2026.07.22]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.22
|
||||||
[2026.07.21]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.21
|
[2026.07.21]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.21
|
||||||
[2026.7.20]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.20
|
[2026.7.20]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.7.20
|
||||||
|
[2026.8.4]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.4
|
||||||
[2026.8.3]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.3
|
[2026.8.3]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.3
|
||||||
[2026.8.2]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.2
|
[2026.8.2]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.2
|
||||||
[2026.8.1]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.1
|
[2026.8.1]: https://git.cbsk-tech.de/Christoph/GitLite/releases/tag/2026.8.1
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Christoph Brandau <c.brandau91@googlemail.com>
|
# Maintainer: Christoph Brandau <c.brandau91@googlemail.com>
|
||||||
|
|
||||||
pkgname=gitty-desktop-bin
|
pkgname=gitty-desktop-bin
|
||||||
pkgver=2026.8.3
|
pkgver=2026.8.4
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="A lightweight, modern Git client built with Tauri (prebuilt AppImage)"
|
pkgdesc="A lightweight, modern Git client built with Tauri (prebuilt AppImage)"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
@@ -14,7 +14,7 @@ options=('!strip')
|
|||||||
|
|
||||||
_appimage="Gitty_${pkgver}_amd64.AppImage"
|
_appimage="Gitty_${pkgver}_amd64.AppImage"
|
||||||
_artifact_url="https://cdn.cbsk-tech.de/gitty/${pkgver}/${_appimage}"
|
_artifact_url="https://cdn.cbsk-tech.de/gitty/${pkgver}/${_appimage}"
|
||||||
_tag=2026.8.3
|
_tag=2026.8.4
|
||||||
source=("${_appimage}::${_artifact_url}"
|
source=("${_appimage}::${_artifact_url}"
|
||||||
"gitty-desktop.png::${url}/raw/tag/${_tag}/src-tauri/icons/icon.png")
|
"gitty-desktop.png::${url}/raw/tag/${_tag}/src-tauri/icons/icon.png")
|
||||||
sha256sums=('SKIP'
|
sha256sums=('SKIP'
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "2026.8.3",
|
"version": "2026.8.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "2026.8.3",
|
"version": "2026.8.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/svelte": "^1.21.0",
|
"@lucide/svelte": "^1.21.0",
|
||||||
"@tailwindcss/vite": "^4.3.1",
|
"@tailwindcss/vite": "^4.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "2026.8.3",
|
"version": "2026.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Gitty",
|
"productName": "Gitty",
|
||||||
"version": "2026.8.3",
|
"version": "2026.8.4",
|
||||||
"identifier": "com.gitty",
|
"identifier": "com.gitty",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
+8
-3
@@ -2656,8 +2656,13 @@
|
|||||||
trackEvent("branch_delete_dialog_opened");
|
trackEvent("branch_delete_dialog_opened");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteBranchFolder(folderName: string, folderBranches: GitBranchInfo[]) {
|
async function deleteBranchFolder(folderName: string, folderBranches: GitBranchInfo[], folderDepth: number) {
|
||||||
if (!activeRepoPath || isBusy) return;
|
if (!activeRepoPath || isBusy) return;
|
||||||
|
const remoteFolder = folderBranches.every((branch) => branch.remote);
|
||||||
|
if (remoteFolder && folderDepth === 0) {
|
||||||
|
errorMessage = "The top-level remote folder cannot be deleted as a group.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
const deletableBranches = folderBranches.filter((branch) => !branch.current);
|
const deletableBranches = folderBranches.filter((branch) => !branch.current);
|
||||||
const currentBranchKept = folderBranches.some((branch) => branch.current);
|
const currentBranchKept = folderBranches.some((branch) => branch.current);
|
||||||
if (deletableBranches.length === 0) {
|
if (deletableBranches.length === 0) {
|
||||||
@@ -2665,7 +2670,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scope = deletableBranches.every((branch) => branch.remote) ? "remote" : "local";
|
const scope = remoteFolder ? "remote" : "local";
|
||||||
const currentNote = currentBranchKept ? "\n\nThe current branch will be kept." : "";
|
const currentNote = currentBranchKept ? "\n\nThe current branch will be kept." : "";
|
||||||
if (!window.confirm(`Delete ${deletableBranches.length} ${scope} branches in “${folderName}”?${currentNote}`)) return;
|
if (!window.confirm(`Delete ${deletableBranches.length} ${scope} branches in “${folderName}”?${currentNote}`)) return;
|
||||||
|
|
||||||
@@ -2691,7 +2696,7 @@
|
|||||||
trackEvent("branch_folder_deleted", {
|
trackEvent("branch_folder_deleted", {
|
||||||
attempted: deletableBranches.length,
|
attempted: deletableBranches.length,
|
||||||
failed: failures.length,
|
failed: failures.length,
|
||||||
remote: scope === "remote" ? 1 : 0,
|
remote: remoteFolder ? 1 : 0,
|
||||||
});
|
});
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
errorMessage = `${failures.length} branch${failures.length === 1 ? "" : "es"} could not be deleted:\n${failures.join("\n")}`;
|
errorMessage = `${failures.length} branch${failures.length === 1 ? "" : "es"} could not be deleted:\n${failures.join("\n")}`;
|
||||||
|
|||||||
+8
-7
@@ -1116,14 +1116,15 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
min-width: 24px;
|
min-width: 26px;
|
||||||
width: 24px;
|
width: 26px;
|
||||||
min-height: 24px;
|
max-width: 26px;
|
||||||
height: 24px;
|
min-height: 26px;
|
||||||
flex: 0 0 24px;
|
height: 26px;
|
||||||
aspect-ratio: 1;
|
max-height: 26px;
|
||||||
|
flex: 0 0 26px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
border-radius: 50%;
|
border-radius: 5px;
|
||||||
opacity: 0.58;
|
opacity: 0.58;
|
||||||
transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
|
transition: opacity 120ms ease, color 120ms ease, background 120ms ease;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
onRenameBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
onRenameBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||||
onDeleteBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
onDeleteBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||||
onDeleteRemoteBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
onDeleteRemoteBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||||
onDeleteBranchFolder: (folderName: string, branches: GitBranchInfo[]) => void | Promise<void>;
|
onDeleteBranchFolder: (folderName: string, branches: GitBranchInfo[], depth: number) => void | Promise<void>;
|
||||||
onCreateTag: (name: string, message: string) => void | Promise<void>;
|
onCreateTag: (name: string, message: string) => void | Promise<void>;
|
||||||
onDeleteTag: (tag: GitTag) => void | Promise<void>;
|
onDeleteTag: (tag: GitTag) => void | Promise<void>;
|
||||||
onPushTag: (tag: GitTag) => void | Promise<void>;
|
onPushTag: (tag: GitTag) => void | Promise<void>;
|
||||||
@@ -283,7 +283,7 @@
|
|||||||
async function openFolderContextMenu(event: MouseEvent, folder: BranchFolderRow) {
|
async function openFolderContextMenu(event: MouseEvent, folder: BranchFolderRow) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (isBusy) return;
|
if (isBusy || (folder.depth === 0 && folder.branches.every((branch) => branch.remote))) return;
|
||||||
|
|
||||||
contextFolder = folder;
|
contextFolder = folder;
|
||||||
contextMenuX = event.clientX + 2;
|
contextMenuX = event.clientX + 2;
|
||||||
@@ -324,9 +324,9 @@
|
|||||||
|
|
||||||
async function deleteContextFolder() {
|
async function deleteContextFolder() {
|
||||||
const folder = contextFolder;
|
const folder = contextFolder;
|
||||||
if (!folder || isBusy) return;
|
if (!folder || isBusy || (folder.depth === 0 && folder.branches.every((branch) => branch.remote))) return;
|
||||||
closeBranchContextMenu();
|
closeBranchContextMenu();
|
||||||
await onDeleteBranchFolder(folder.name, folder.branches);
|
await onDeleteBranchFolder(folder.name, folder.branches, folder.depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createContextWorktree() {
|
async function createContextWorktree() {
|
||||||
@@ -584,7 +584,7 @@
|
|||||||
style={`--branch-indent: ${row.depth * 16}px;`}
|
style={`--branch-indent: ${row.depth * 16}px;`}
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => toggleBranchFolder(row.id)}
|
onclick={() => toggleBranchFolder(row.id)}
|
||||||
oncontextmenu={(event) => openFolderContextMenu(event, row)}
|
oncontextmenu={row.depth > 0 ? (event) => openFolderContextMenu(event, row) : undefined}
|
||||||
aria-expanded={isBranchFolderOpen(row.id)}
|
aria-expanded={isBranchFolderOpen(row.id)}
|
||||||
title={`${row.name} (${row.branchCount})`}
|
title={`${row.name} (${row.branchCount})`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1399,6 +1399,17 @@
|
|||||||
label: "Neu in Gitty",
|
label: "Neu in Gitty",
|
||||||
description: "Änderungen seit der letzten veröffentlichten Version und wichtige Neuerungen früherer Releases.",
|
description: "Änderungen seit der letzten veröffentlichten Version und wichtige Neuerungen früherer Releases.",
|
||||||
sections: [
|
sections: [
|
||||||
|
{
|
||||||
|
id: "changelog-2026-8-4",
|
||||||
|
title: "Version 2026.8.4",
|
||||||
|
summary: "Dieses Release vereinfacht die Verwaltung zusammengehöriger Branches und sorgt für eine einheitlichere, klarere Oberfläche.",
|
||||||
|
steps: [
|
||||||
|
"Lokale und verschachtelte Remote-Branch-Ordner lassen sich über ihr Kontextmenü gesammelt löschen. Der oberste Remote-Ordner wie origin ist geschützt. Der aktuell ausgecheckte Branch bleibt erhalten und einzelne Fehler werden nach Abschluss verständlich aufgeführt.",
|
||||||
|
"Der Compare-Auswahldialog ist vollständig auf Deutsch verfügbar und orientiert sich bei Feldern, Gruppen, Typografie und Dialogflächen am Styling der externen Tools.",
|
||||||
|
"Die Schließen-Schaltflächen der Repository-Tabs sind quadratisch und haben ausgewogenere Abstände sowie deutlichere Hover- und Tastaturfokus-Zustände.",
|
||||||
|
],
|
||||||
|
note: "Der oberste Remote-Ordner wie origin kann nicht gesammelt gelöscht werden. Seine Unterordner können weiterhin gezielt verwaltet werden.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "changelog-2026-8-3",
|
id: "changelog-2026-8-3",
|
||||||
title: "Version 2026.8.3",
|
title: "Version 2026.8.3",
|
||||||
@@ -1488,6 +1499,17 @@
|
|||||||
label: "What's new",
|
label: "What's new",
|
||||||
description: "Changes since the latest published version and notable additions from earlier releases.",
|
description: "Changes since the latest published version and notable additions from earlier releases.",
|
||||||
sections: [
|
sections: [
|
||||||
|
{
|
||||||
|
id: "changelog-2026-8-4",
|
||||||
|
title: "Version 2026.8.4",
|
||||||
|
summary: "This release simplifies managing related branches and makes the interface more consistent and easier to read.",
|
||||||
|
steps: [
|
||||||
|
"Local and nested remote branch folders can be deleted in one action from their context menu. The top-level remote folder such as origin is protected. The currently checked-out branch is kept, and individual failures are summarized after processing.",
|
||||||
|
"The Compare selector is fully localized in German and now follows the external-tool selectors for fields, groups, typography, and dialog surfaces.",
|
||||||
|
"Repository-tab close buttons are square and have more balanced spacing and clearer hover and keyboard-focus states.",
|
||||||
|
],
|
||||||
|
note: "The top-level remote folder such as origin cannot be deleted in bulk. Its nested folders can still be managed selectively.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "changelog-2026-8-3",
|
id: "changelog-2026-8-3",
|
||||||
title: "Version 2026.8.3",
|
title: "Version 2026.8.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user