mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
fix: delete and checkout now work properly - checkout local only / remote only branches - delete local only / not remote branches if !dev mode
This commit is contained in:
@@ -5,20 +5,31 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def checkout_branch(repo_path, branch_name):
|
||||
try:
|
||||
logger.debug(f"Attempting to checkout branch: {branch_name}")
|
||||
repo = git.Repo(repo_path)
|
||||
|
||||
# Check if the branch exists
|
||||
if branch_name not in repo.heads:
|
||||
return False, f"Branch '{branch_name}' does not exist."
|
||||
|
||||
# Checkout the branch
|
||||
repo.git.checkout(branch_name)
|
||||
# Check if the branch exists locally
|
||||
if branch_name in repo.heads:
|
||||
repo.git.checkout(branch_name)
|
||||
else:
|
||||
# Check if the branch exists in any of the remotes
|
||||
for remote in repo.remotes:
|
||||
remote_branch = f"{remote.name}/{branch_name}"
|
||||
if remote_branch in repo.refs:
|
||||
# Create a new local branch tracking the remote branch
|
||||
repo.git.checkout('-b', branch_name, remote_branch)
|
||||
break
|
||||
else:
|
||||
return False, f"Branch '{branch_name}' does not exist locally or in any remote."
|
||||
|
||||
logger.debug(f"Successfully checked out branch: {branch_name}")
|
||||
return True, {"message": f"Checked out branch: {branch_name}", "current_branch": branch_name}
|
||||
return True, {
|
||||
"message": f"Checked out branch: {branch_name}",
|
||||
"current_branch": branch_name
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking out branch: {str(e)}", exc_info=True)
|
||||
return False, {"error": f"Error checking out branch: {str(e)}"}
|
||||
return False, {"error": f"Error checking out branch: {str(e)}"}
|
||||
|
||||
@@ -7,10 +7,12 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def delete_branch(repo_path, branch_name):
|
||||
try:
|
||||
logger.debug(f"Attempting to delete branch: {branch_name}")
|
||||
logger.debug(f"Attempting to delete branch from repo at path: {repo_path}")
|
||||
logger.debug(
|
||||
f"Attempting to delete branch from repo at path: {repo_path}")
|
||||
repo = git.Repo(repo_path)
|
||||
|
||||
# Fetch updates from remote
|
||||
@@ -29,15 +31,24 @@ def delete_branch(repo_path, branch_name):
|
||||
repo.delete_head(branch_name, force=True)
|
||||
logger.debug(f"Local branch {branch_name} deleted")
|
||||
|
||||
# Attempt to delete remote branch
|
||||
try:
|
||||
# Check if remote branch exists
|
||||
remote_branch = f"origin/{branch_name}"
|
||||
if remote_branch in repo.refs:
|
||||
logger.debug(f"Attempting to delete remote branch: {branch_name}")
|
||||
repo.git.push('origin', '--delete', branch_name)
|
||||
logger.debug(f"Successfully deleted remote branch: {branch_name}")
|
||||
return True, {"message": f"Deleted branch: {branch_name}", "current_branch": repo.active_branch.name}
|
||||
except git.GitCommandError as e:
|
||||
logger.error(f"Failed to delete remote branch: {branch_name}. Error: {str(e)}")
|
||||
return False, f"Failed to delete remote branch: {branch_name}. It may not exist or there might be permission issues."
|
||||
try:
|
||||
repo.git.push('origin', '--delete', branch_name)
|
||||
logger.debug(
|
||||
f"Successfully deleted remote branch: {branch_name}")
|
||||
except GitCommandError as e:
|
||||
logger.error(
|
||||
f"Failed to delete remote branch: {branch_name}. Error: {str(e)}"
|
||||
)
|
||||
return False, f"Failed to delete remote branch: {branch_name}. There might be permission issues."
|
||||
|
||||
return True, {
|
||||
"message": f"Deleted branch: {branch_name}",
|
||||
"current_branch": repo.active_branch.name
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting branch: {str(e)}", exc_info=True)
|
||||
|
||||
@@ -1,352 +1,432 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Modal from "../ui/Modal";
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import Modal from '../ui/Modal';
|
||||
import {
|
||||
getBranches,
|
||||
checkoutBranch,
|
||||
createBranch,
|
||||
deleteBranch,
|
||||
pushBranchToRemote,
|
||||
} from "../../api/api";
|
||||
getBranches,
|
||||
checkoutBranch,
|
||||
createBranch,
|
||||
deleteBranch,
|
||||
pushBranchToRemote
|
||||
} from '../../api/api';
|
||||
import {
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
GitBranchPlus,
|
||||
ArrowRightCircle,
|
||||
Loader,
|
||||
CloudUpload,
|
||||
} from "lucide-react";
|
||||
import Tooltip from "../ui/Tooltip";
|
||||
import Alert from "../ui/Alert";
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
GitBranchPlus,
|
||||
ArrowRightCircle,
|
||||
Loader,
|
||||
CloudUpload
|
||||
} from 'lucide-react';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
import Alert from '../ui/Alert';
|
||||
|
||||
const SettingsBranchModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
repoUrl,
|
||||
currentBranch,
|
||||
onBranchChange,
|
||||
isOpen,
|
||||
onClose,
|
||||
repoUrl,
|
||||
currentBranch,
|
||||
onBranchChange,
|
||||
isDevMode
|
||||
}) => {
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [branchOffMode, setBranchOffMode] = useState(null);
|
||||
const [newBranchName, setNewBranchName] = useState("");
|
||||
const [validBranchName, setValidBranchName] = useState(true);
|
||||
const [branchToDelete, setBranchToDelete] = useState(null);
|
||||
const [loadingAction, setLoadingAction] = useState("");
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [branchOffMode, setBranchOffMode] = useState(null);
|
||||
const [newBranchName, setNewBranchName] = useState('');
|
||||
const [validBranchName, setValidBranchName] = useState(true);
|
||||
const [branchToDelete, setBranchToDelete] = useState(null);
|
||||
const [loadingAction, setLoadingAction] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchBranches();
|
||||
resetForm();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const fetchBranches = async () => {
|
||||
try {
|
||||
const response = await getBranches();
|
||||
if (response.success && response.data.branches) {
|
||||
setBranches(response.data.branches);
|
||||
} else {
|
||||
console.error("Error fetching branches:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching branches:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setBranchOffMode(null);
|
||||
setNewBranchName("");
|
||||
setValidBranchName(true);
|
||||
setBranchToDelete(null);
|
||||
setLoadingAction("");
|
||||
};
|
||||
|
||||
const handleCheckout = async (branchName) => {
|
||||
setLoadingAction(`checkout-${branchName}`);
|
||||
try {
|
||||
const response = await checkoutBranch(branchName);
|
||||
if (response.success) {
|
||||
await fetchBranches();
|
||||
onBranchChange();
|
||||
Alert.success("Branch checked out successfully");
|
||||
onClose();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400 && error.response.data.error) {
|
||||
Alert.error(error.response.data.error);
|
||||
} else {
|
||||
Alert.error("An unexpected error occurred while checking out the branch.");
|
||||
console.error("Error checking out branch:", error);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleBranchOff = async () => {
|
||||
setLoadingAction("branchOff");
|
||||
if (newBranchName && validBranchName) {
|
||||
try {
|
||||
const response = await createBranch(newBranchName, branchOffMode);
|
||||
if (response.success) {
|
||||
await fetchBranches();
|
||||
onBranchChange();
|
||||
Alert.success("Branch created successfully");
|
||||
resetForm();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchBranches();
|
||||
resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400 && error.response.data.error) {
|
||||
Alert.error(error.response.data.error);
|
||||
} else {
|
||||
console.error("Error branching off:", error);
|
||||
Alert.error("An unexpected error occurred while creating the branch. Please try again.");
|
||||
}, [isOpen]);
|
||||
|
||||
const fetchBranches = async () => {
|
||||
try {
|
||||
const response = await getBranches();
|
||||
if (response.success && response.data.branches) {
|
||||
setBranches(response.data.branches);
|
||||
} else {
|
||||
console.error('Error fetching branches:', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching branches:', error);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction("");
|
||||
}
|
||||
} else {
|
||||
Alert.error("Please enter a valid branch name.");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleBranchOffClick = (branchName) => {
|
||||
setBranchOffMode(branchName);
|
||||
setNewBranchName("");
|
||||
setValidBranchName(true);
|
||||
};
|
||||
|
||||
const validateBranchName = (name) => {
|
||||
const isValid = /^[a-zA-Z0-9._-]+$/.test(name);
|
||||
setValidBranchName(isValid);
|
||||
setNewBranchName(name);
|
||||
};
|
||||
|
||||
const handleOpenInGitHub = (branchName) => {
|
||||
const branchUrl = `${repoUrl}/tree/${encodeURIComponent(branchName)}`;
|
||||
window.open(branchUrl, "_blank");
|
||||
};
|
||||
|
||||
const confirmDeleteBranch = (branchName) => {
|
||||
setBranchToDelete(branchName);
|
||||
};
|
||||
|
||||
const handleDeleteBranch = async () => {
|
||||
if (branchToDelete && branchToDelete.toLowerCase() === "main") {
|
||||
Alert.warning("The 'main' branch cannot be deleted.");
|
||||
return;
|
||||
}
|
||||
setLoadingAction(`delete-${branchToDelete}`);
|
||||
try {
|
||||
const response = await deleteBranch(branchToDelete);
|
||||
if (response.success) {
|
||||
onBranchChange();
|
||||
await fetchBranches();
|
||||
Alert.success(`Branch '${branchToDelete}' deleted successfully`);
|
||||
const resetForm = () => {
|
||||
setBranchOffMode(null);
|
||||
setNewBranchName('');
|
||||
setValidBranchName(true);
|
||||
setBranchToDelete(null);
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.error("An unexpected error occurred while deleting the branch.");
|
||||
console.error("Error deleting branch:", error);
|
||||
} finally {
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
setLoadingAction('');
|
||||
};
|
||||
|
||||
const handlePushToRemote = async (branchName) => {
|
||||
setLoadingAction(`push-${branchName}`);
|
||||
try {
|
||||
const response = await pushBranchToRemote(branchName);
|
||||
if (response.success) {
|
||||
Alert.success(`Branch '${branchName}' pushed to remote successfully`);
|
||||
await fetchBranches();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.error("An unexpected error occurred while pushing the branch to remote.");
|
||||
console.error("Error pushing branch to remote:", error);
|
||||
} finally {
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
const handleCheckout = async branchName => {
|
||||
setLoadingAction(`checkout-${branchName}`);
|
||||
try {
|
||||
const response = await checkoutBranch(branchName);
|
||||
if (response.success) {
|
||||
await fetchBranches();
|
||||
onBranchChange();
|
||||
Alert.success('Branch checked out successfully');
|
||||
onClose();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.status === 400 &&
|
||||
error.response.data.error
|
||||
) {
|
||||
Alert.error(error.response.data.error);
|
||||
} else {
|
||||
Alert.error(
|
||||
'An unexpected error occurred while checking out the branch.'
|
||||
);
|
||||
console.error('Error checking out branch:', error);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Manage Git Branches">
|
||||
<div className="space-y-6 text-sm">
|
||||
<div className="bg-gray-800 rounded-lg p-4 shadow-inner">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-100 border-b border-gray-700 pb-2">
|
||||
Branches
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{branches.map((branch, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`flex items-center justify-between p-3 rounded-md transition-all duration-200 ${
|
||||
branch.name === currentBranch
|
||||
? "bg-blue-900/30 border border-blue-500 shadow-md"
|
||||
: "bg-gray-700 hover:bg-gray-650"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
branch.name === currentBranch
|
||||
? "bg-blue-400"
|
||||
: "bg-gray-400"
|
||||
}`}
|
||||
></div>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
branch.name === currentBranch
|
||||
? "text-blue-200"
|
||||
: "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
{branch.name || "Unknown Branch"}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{branch.isLocal && !branch.isRemote
|
||||
? "(Local)"
|
||||
: !branch.isLocal && branch.isRemote
|
||||
? "(Remote)"
|
||||
: "(Local & Remote)"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{branch.name !== currentBranch && (
|
||||
<Tooltip content="Checkout">
|
||||
<button
|
||||
onClick={() => handleCheckout(branch.name)}
|
||||
className="p-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === `checkout-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `checkout-${branch.name}` ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircle size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content="Branch Off">
|
||||
<button
|
||||
onClick={() => handleBranchOffClick(branch.name)}
|
||||
className="p-1.5 bg-green-500 text-white rounded-md hover:bg-green-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === "branchOff"}
|
||||
>
|
||||
{loadingAction === "branchOff" ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<GitBranchPlus size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{branch.isLocal && !branch.isRemote && (
|
||||
<Tooltip content="Push to Remote">
|
||||
<button
|
||||
onClick={() => handlePushToRemote(branch.name)}
|
||||
className="p-1.5 bg-purple-500 text-white rounded-md hover:bg-purple-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === `push-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `push-${branch.name}` ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<CloudUpload size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{branch.isLocal && branch.name !== currentBranch && branch.name.toLowerCase() !== "main" && (
|
||||
<Tooltip content="Delete">
|
||||
<button
|
||||
onClick={() => confirmDeleteBranch(branch.name)}
|
||||
className="p-1.5 bg-red-500 text-white rounded-md hover:bg-red-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === `delete-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `delete-${branch.name}` ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<Trash2 size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content="View on GitHub">
|
||||
<button
|
||||
onClick={() => handleOpenInGitHub(branch.name)}
|
||||
className="p-1.5 bg-gray-600 text-white rounded-md hover:bg-gray-500 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
>
|
||||
<ExternalLink size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{branchOffMode && (
|
||||
<div className="bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<h4 className="text-sm font-semibold mb-2 text-gray-200">
|
||||
Create New Branch
|
||||
</h4>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newBranchName}
|
||||
onChange={(e) => validateBranchName(e.target.value)}
|
||||
placeholder={`New branch from ${branchOffMode}`}
|
||||
className={`flex-grow p-2 border rounded bg-gray-800 text-gray-300 focus:ring-2 focus:ring-blue-500 transition-all ${
|
||||
!validBranchName ? "border-red-500" : "border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
<button
|
||||
onClick={handleBranchOff}
|
||||
disabled={
|
||||
!newBranchName ||
|
||||
!validBranchName ||
|
||||
loadingAction === "branchOff"
|
||||
const handleBranchOff = async () => {
|
||||
setLoadingAction('branchOff');
|
||||
if (newBranchName && validBranchName) {
|
||||
try {
|
||||
const response = await createBranch(
|
||||
newBranchName,
|
||||
branchOffMode
|
||||
);
|
||||
if (response.success) {
|
||||
await fetchBranches();
|
||||
onBranchChange();
|
||||
Alert.success('Branch created successfully');
|
||||
resetForm();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
{loadingAction === "branchOff" ? "Creating..." : "Create"}
|
||||
</button>
|
||||
} catch (error) {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.status === 400 &&
|
||||
error.response.data.error
|
||||
) {
|
||||
Alert.error(error.response.data.error);
|
||||
} else {
|
||||
console.error('Error branching off:', error);
|
||||
Alert.error(
|
||||
'An unexpected error occurred while creating the branch. Please try again.'
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
}
|
||||
} else {
|
||||
Alert.error('Please enter a valid branch name.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBranchOffClick = branchName => {
|
||||
setBranchOffMode(branchName);
|
||||
setNewBranchName('');
|
||||
setValidBranchName(true);
|
||||
};
|
||||
|
||||
const validateBranchName = name => {
|
||||
const isValid = /^[a-zA-Z0-9._-]+$/.test(name);
|
||||
setValidBranchName(isValid);
|
||||
setNewBranchName(name);
|
||||
};
|
||||
|
||||
const handleOpenInGitHub = branchName => {
|
||||
const branchUrl = `${repoUrl}/tree/${encodeURIComponent(branchName)}`;
|
||||
window.open(branchUrl, '_blank');
|
||||
};
|
||||
|
||||
const confirmDeleteBranch = branchName => {
|
||||
setBranchToDelete(branchName);
|
||||
};
|
||||
|
||||
const handleDeleteBranch = async () => {
|
||||
if (branchToDelete && branchToDelete.toLowerCase() === 'main') {
|
||||
Alert.warning("The 'main' branch cannot be deleted.");
|
||||
return;
|
||||
}
|
||||
setLoadingAction(`delete-${branchToDelete}`);
|
||||
try {
|
||||
const response = await deleteBranch(branchToDelete);
|
||||
if (response.success) {
|
||||
onBranchChange();
|
||||
await fetchBranches();
|
||||
Alert.success(
|
||||
`Branch '${branchToDelete}' deleted successfully`
|
||||
);
|
||||
setBranchToDelete(null);
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.error(
|
||||
'An unexpected error occurred while deleting the branch.'
|
||||
);
|
||||
console.error('Error deleting branch:', error);
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePushToRemote = async branchName => {
|
||||
setLoadingAction(`push-${branchName}`);
|
||||
try {
|
||||
const response = await pushBranchToRemote(branchName);
|
||||
if (response.success) {
|
||||
Alert.success(
|
||||
`Branch '${branchName}' pushed to remote successfully`
|
||||
);
|
||||
await fetchBranches();
|
||||
} else {
|
||||
Alert.error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.error(
|
||||
'An unexpected error occurred while pushing the branch to remote.'
|
||||
);
|
||||
console.error('Error pushing branch to remote:', error);
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title='Manage Git Branches'>
|
||||
<div className='space-y-6 text-sm'>
|
||||
<div className='bg-gray-800 rounded-lg p-4 shadow-inner'>
|
||||
<h3 className='text-lg font-semibold mb-3 text-gray-100 border-b border-gray-700 pb-2'>
|
||||
Branches
|
||||
</h3>
|
||||
<ul className='space-y-3'>
|
||||
{branches.map((branch, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`flex items-center justify-between p-3 rounded-md transition-all duration-200 ${
|
||||
branch.name === currentBranch
|
||||
? 'bg-blue-900/30 border border-blue-500 shadow-md'
|
||||
: 'bg-gray-700 hover:bg-gray-650'
|
||||
}`}>
|
||||
<div className='flex items-center space-x-3'>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
branch.name === currentBranch
|
||||
? 'bg-blue-400'
|
||||
: 'bg-gray-400'
|
||||
}`}></div>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
branch.name === currentBranch
|
||||
? 'text-blue-200'
|
||||
: 'text-gray-200'
|
||||
}`}>
|
||||
{branch.name || 'Unknown Branch'}
|
||||
</span>
|
||||
<span className='text-xs text-gray-400'>
|
||||
{branch.isLocal && !branch.isRemote
|
||||
? '(Local)'
|
||||
: !branch.isLocal && branch.isRemote
|
||||
? '(Remote)'
|
||||
: '(Local & Remote)'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
{branch.name !== currentBranch && (
|
||||
<Tooltip content='Checkout'>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleCheckout(branch.name)
|
||||
}
|
||||
className='p-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 hover:scale-105 transition-all duration-200 shadow-sm'
|
||||
disabled={
|
||||
loadingAction ===
|
||||
`checkout-${branch.name}`
|
||||
}>
|
||||
{loadingAction ===
|
||||
`checkout-${branch.name}` ? (
|
||||
<Loader
|
||||
size={16}
|
||||
className='animate-spin'
|
||||
/>
|
||||
) : (
|
||||
<ArrowRightCircle
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content='Branch Off'>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleBranchOffClick(
|
||||
branch.name
|
||||
)
|
||||
}
|
||||
className='p-1.5 bg-green-500 text-white rounded-md hover:bg-green-600 hover:scale-105 transition-all duration-200 shadow-sm'
|
||||
disabled={
|
||||
loadingAction === 'branchOff'
|
||||
}>
|
||||
{loadingAction === 'branchOff' ? (
|
||||
<Loader
|
||||
size={16}
|
||||
className='animate-spin'
|
||||
/>
|
||||
) : (
|
||||
<GitBranchPlus size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{branch.isLocal &&
|
||||
!branch.isRemote &&
|
||||
isDevMode && (
|
||||
<Tooltip content='Push to Remote'>
|
||||
<button
|
||||
onClick={() =>
|
||||
handlePushToRemote(
|
||||
branch.name
|
||||
)
|
||||
}
|
||||
className='p-1.5 bg-purple-500 text-white rounded-md hover:bg-purple-600 hover:scale-105 transition-all duration-200 shadow-sm'
|
||||
disabled={
|
||||
loadingAction ===
|
||||
`push-${branch.name}`
|
||||
}>
|
||||
{loadingAction ===
|
||||
`push-${branch.name}` ? (
|
||||
<Loader
|
||||
size={16}
|
||||
className='animate-spin'
|
||||
/>
|
||||
) : (
|
||||
<CloudUpload
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(branch.isLocal ||
|
||||
(branch.isRemote && isDevMode)) &&
|
||||
branch.name !== currentBranch &&
|
||||
branch.name.toLowerCase() !==
|
||||
'stable' && (
|
||||
<Tooltip content='Delete'>
|
||||
<button
|
||||
onClick={() =>
|
||||
confirmDeleteBranch(
|
||||
branch.name
|
||||
)
|
||||
}
|
||||
className='p-1.5 bg-red-500 text-white rounded-md hover:bg-red-600 hover:scale-105 transition-all duration-200 shadow-sm'
|
||||
disabled={
|
||||
loadingAction ===
|
||||
`delete-${branch.name}`
|
||||
}>
|
||||
{loadingAction ===
|
||||
`delete-${branch.name}` ? (
|
||||
<Loader
|
||||
size={16}
|
||||
className='animate-spin'
|
||||
/>
|
||||
) : (
|
||||
<Trash2 size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{branch.isRemote && (
|
||||
<Tooltip content='View on GitHub'>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleOpenInGitHub(
|
||||
branch.name
|
||||
)
|
||||
}
|
||||
className='p-1.5 bg-gray-600 text-white rounded-md hover:bg-gray-500 hover:scale-105 transition-all duration-200 shadow-sm'>
|
||||
<ExternalLink size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{branchOffMode && (
|
||||
<div className='bg-gray-700 p-4 rounded-lg shadow-md'>
|
||||
<h4 className='text-sm font-semibold mb-2 text-gray-200'>
|
||||
Create New Branch
|
||||
</h4>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<input
|
||||
type='text'
|
||||
value={newBranchName}
|
||||
onChange={e =>
|
||||
validateBranchName(e.target.value)
|
||||
}
|
||||
placeholder={`New branch from ${branchOffMode}`}
|
||||
className={`flex-grow p-2 border rounded bg-gray-800 text-gray-300 focus:ring-2 focus:ring-blue-500 transition-all ${
|
||||
!validBranchName
|
||||
? 'border-red-500'
|
||||
: 'border-gray-600'
|
||||
}`}
|
||||
/>
|
||||
<button
|
||||
onClick={handleBranchOff}
|
||||
disabled={
|
||||
!newBranchName ||
|
||||
!validBranchName ||
|
||||
loadingAction === 'branchOff'
|
||||
}
|
||||
className='px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors text-sm font-medium shadow-sm'>
|
||||
{loadingAction === 'branchOff'
|
||||
? 'Creating...'
|
||||
: 'Create'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{branchToDelete && (
|
||||
<div className='bg-red-900/30 border border-red-500 rounded-lg p-4 mt-4 text-sm text-gray-200'>
|
||||
<p className='mb-3'>
|
||||
Are you sure you want to delete the branch{' '}
|
||||
<strong className='text-red-300'>
|
||||
{branchToDelete}
|
||||
</strong>
|
||||
? This action cannot be undone.
|
||||
</p>
|
||||
<div className='flex space-x-4'>
|
||||
<button
|
||||
onClick={handleDeleteBranch}
|
||||
disabled={
|
||||
loadingAction === `delete-${branchToDelete}`
|
||||
}
|
||||
className='px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm font-medium shadow-sm'>
|
||||
{loadingAction === `delete-${branchToDelete}`
|
||||
? 'Deleting...'
|
||||
: 'Confirm Delete'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBranchToDelete(null)}
|
||||
className='px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition-colors text-sm font-medium shadow-sm'>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{branchToDelete && (
|
||||
<div className="bg-red-900/30 border border-red-500 rounded-lg p-4 mt-4 text-sm text-gray-200">
|
||||
<p className="mb-3">
|
||||
Are you sure you want to delete the branch{" "}
|
||||
<strong className="text-red-300">{branchToDelete}</strong>? This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
onClick={handleDeleteBranch}
|
||||
disabled={loadingAction === `delete-${branchToDelete}`}
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
{loadingAction === `delete-${branchToDelete}`
|
||||
? "Deleting..."
|
||||
: "Confirm Delete"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBranchToDelete(null)}
|
||||
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsBranchModal;
|
||||
export default SettingsBranchModal;
|
||||
|
||||
@@ -872,6 +872,7 @@ const SettingsPage = () => {
|
||||
type={currentChange.type}
|
||||
name={currentChange.name}
|
||||
commitMessage={currentChange.commit_message}
|
||||
isDevMode={isDevMode}
|
||||
/>
|
||||
)}
|
||||
<LinkRepoModal
|
||||
|
||||
Reference in New Issue
Block a user