mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-13 13:49:43 +00:00
fix: initial commit error
This commit is contained in:
@@ -80,34 +80,47 @@ async function validateGitRepository(projectPath) {
|
|||||||
// Get git status for a project
|
// Get git status for a project
|
||||||
router.get('/status', async (req, res) => {
|
router.get('/status', async (req, res) => {
|
||||||
const { project } = req.query;
|
const { project } = req.query;
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return res.status(400).json({ error: 'Project name is required' });
|
return res.status(400).json({ error: 'Project name is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const projectPath = await getActualProjectPath(project);
|
const projectPath = await getActualProjectPath(project);
|
||||||
|
|
||||||
// Validate git repository
|
// Validate git repository
|
||||||
await validateGitRepository(projectPath);
|
await validateGitRepository(projectPath);
|
||||||
|
|
||||||
// Get current branch
|
// Get current branch - handle case where there are no commits yet
|
||||||
const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath });
|
let branch = 'main';
|
||||||
|
let hasCommits = true;
|
||||||
|
try {
|
||||||
|
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath });
|
||||||
|
branch = branchOutput.trim();
|
||||||
|
} catch (error) {
|
||||||
|
// No HEAD exists - repository has no commits yet
|
||||||
|
if (error.message.includes('unknown revision') || error.message.includes('ambiguous argument')) {
|
||||||
|
hasCommits = false;
|
||||||
|
branch = 'main';
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get git status
|
// Get git status
|
||||||
const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: projectPath });
|
const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: projectPath });
|
||||||
|
|
||||||
const modified = [];
|
const modified = [];
|
||||||
const added = [];
|
const added = [];
|
||||||
const deleted = [];
|
const deleted = [];
|
||||||
const untracked = [];
|
const untracked = [];
|
||||||
|
|
||||||
statusOutput.split('\n').forEach(line => {
|
statusOutput.split('\n').forEach(line => {
|
||||||
if (!line.trim()) return;
|
if (!line.trim()) return;
|
||||||
|
|
||||||
const status = line.substring(0, 2);
|
const status = line.substring(0, 2);
|
||||||
const file = line.substring(3);
|
const file = line.substring(3);
|
||||||
|
|
||||||
if (status === 'M ' || status === ' M' || status === 'MM') {
|
if (status === 'M ' || status === ' M' || status === 'MM') {
|
||||||
modified.push(file);
|
modified.push(file);
|
||||||
} else if (status === 'A ' || status === 'AM') {
|
} else if (status === 'A ' || status === 'AM') {
|
||||||
@@ -118,9 +131,10 @@ router.get('/status', async (req, res) => {
|
|||||||
untracked.push(file);
|
untracked.push(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
branch: branch.trim(),
|
branch,
|
||||||
|
hasCommits,
|
||||||
modified,
|
modified,
|
||||||
added,
|
added,
|
||||||
deleted,
|
deleted,
|
||||||
@@ -128,9 +142,9 @@ router.get('/status', async (req, res) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Git status error:', error);
|
console.error('Git status error:', error);
|
||||||
res.json({
|
res.json({
|
||||||
error: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository')
|
error: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository')
|
||||||
? error.message
|
? error.message
|
||||||
: 'Git operation failed',
|
: 'Git operation failed',
|
||||||
details: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository')
|
details: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository')
|
||||||
? error.message
|
? error.message
|
||||||
@@ -264,6 +278,50 @@ router.get('/file-with-diff', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create initial commit
|
||||||
|
router.post('/initial-commit', async (req, res) => {
|
||||||
|
const { project } = req.body;
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
return res.status(400).json({ error: 'Project name is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const projectPath = await getActualProjectPath(project);
|
||||||
|
|
||||||
|
// Validate git repository
|
||||||
|
await validateGitRepository(projectPath);
|
||||||
|
|
||||||
|
// Check if there are already commits
|
||||||
|
try {
|
||||||
|
await execAsync('git rev-parse HEAD', { cwd: projectPath });
|
||||||
|
return res.status(400).json({ error: 'Repository already has commits. Use regular commit instead.' });
|
||||||
|
} catch (error) {
|
||||||
|
// No HEAD - this is good, we can create initial commit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all files
|
||||||
|
await execAsync('git add .', { cwd: projectPath });
|
||||||
|
|
||||||
|
// Create initial commit
|
||||||
|
const { stdout } = await execAsync('git commit -m "Initial commit"', { cwd: projectPath });
|
||||||
|
|
||||||
|
res.json({ success: true, output: stdout, message: 'Initial commit created successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Git initial commit error:', error);
|
||||||
|
|
||||||
|
// Handle the case where there's nothing to commit
|
||||||
|
if (error.message.includes('nothing to commit')) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Nothing to commit',
|
||||||
|
details: 'No files found in the repository. Add some files first.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Commit changes
|
// Commit changes
|
||||||
router.post('/commit', async (req, res) => {
|
router.post('/commit', async (req, res) => {
|
||||||
const { project, message, files } = req.body;
|
const { project, message, files } = req.body;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
|
|||||||
const [isPublishing, setIsPublishing] = useState(false);
|
const [isPublishing, setIsPublishing] = useState(false);
|
||||||
const [isCommitAreaCollapsed, setIsCommitAreaCollapsed] = useState(isMobile); // Collapsed by default on mobile
|
const [isCommitAreaCollapsed, setIsCommitAreaCollapsed] = useState(isMobile); // Collapsed by default on mobile
|
||||||
const [confirmAction, setConfirmAction] = useState(null); // { type: 'discard|commit|pull|push', file?: string, message?: string }
|
const [confirmAction, setConfirmAction] = useState(null); // { type: 'discard|commit|pull|push', file?: string, message?: string }
|
||||||
|
const [isCreatingInitialCommit, setIsCreatingInitialCommit] = useState(false);
|
||||||
const textareaRef = useRef(null);
|
const textareaRef = useRef(null);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
|
|
||||||
@@ -547,7 +548,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
|
|||||||
|
|
||||||
const handleCommit = async () => {
|
const handleCommit = async () => {
|
||||||
if (!commitMessage.trim() || selectedFiles.size === 0) return;
|
if (!commitMessage.trim() || selectedFiles.size === 0) return;
|
||||||
|
|
||||||
setIsCommitting(true);
|
setIsCommitting(true);
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch('/api/git/commit', {
|
const response = await authenticatedFetch('/api/git/commit', {
|
||||||
@@ -559,7 +560,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
|
|||||||
files: Array.from(selectedFiles)
|
files: Array.from(selectedFiles)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Reset state after successful commit
|
// Reset state after successful commit
|
||||||
@@ -577,6 +578,32 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createInitialCommit = async () => {
|
||||||
|
setIsCreatingInitialCommit(true);
|
||||||
|
try {
|
||||||
|
const response = await authenticatedFetch('/api/git/initial-commit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
project: selectedProject.name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
fetchGitStatus();
|
||||||
|
fetchRemoteStatus();
|
||||||
|
} else {
|
||||||
|
console.error('Initial commit failed:', data.error);
|
||||||
|
alert(data.error || 'Failed to create initial commit');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating initial commit:', error);
|
||||||
|
alert('Failed to create initial commit');
|
||||||
|
} finally {
|
||||||
|
setIsCreatingInitialCommit(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusLabel = (status) => {
|
const getStatusLabel = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -1161,6 +1188,31 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
|
|||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
<RefreshCw className="w-6 h-6 animate-spin text-gray-400" />
|
<RefreshCw className="w-6 h-6 animate-spin text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
|
) : gitStatus?.hasCommits === false ? (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8 text-center">
|
||||||
|
<GitBranch className="w-16 h-16 mb-4 opacity-30 text-gray-400 dark:text-gray-500" />
|
||||||
|
<h3 className="text-lg font-medium mb-2 text-gray-900 dark:text-white">No commits yet</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6 max-w-md">
|
||||||
|
This repository doesn't have any commits yet. Create your first commit to start tracking changes.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={createInitialCommit}
|
||||||
|
disabled={isCreatingInitialCommit}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{isCreatingInitialCommit ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||||
|
<span>Creating Initial Commit...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<GitCommit className="w-4 h-4" />
|
||||||
|
<span>Create Initial Commit</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : !gitStatus || (!gitStatus.modified?.length && !gitStatus.added?.length && !gitStatus.deleted?.length && !gitStatus.untracked?.length) ? (
|
) : !gitStatus || (!gitStatus.modified?.length && !gitStatus.added?.length && !gitStatus.deleted?.length && !gitStatus.untracked?.length) ? (
|
||||||
<div className="flex flex-col items-center justify-center h-32 text-gray-500 dark:text-gray-400">
|
<div className="flex flex-col items-center justify-center h-32 text-gray-500 dark:text-gray-400">
|
||||||
<GitCommit className="w-12 h-12 mb-2 opacity-50" />
|
<GitCommit className="w-12 h-12 mb-2 opacity-50" />
|
||||||
|
|||||||
Reference in New Issue
Block a user