diff --git a/server/routes/git.js b/server/routes/git.js index 6fc6a0a..0df4e44 100755 --- a/server/routes/git.js +++ b/server/routes/git.js @@ -80,34 +80,47 @@ async function validateGitRepository(projectPath) { // Get git status for a project router.get('/status', async (req, res) => { const { project } = req.query; - + if (!project) { return res.status(400).json({ error: 'Project name is required' }); } try { const projectPath = await getActualProjectPath(project); - + // Validate git repository await validateGitRepository(projectPath); - // Get current branch - const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath }); - + // Get current branch - handle case where there are no commits yet + 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 const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: projectPath }); - + const modified = []; const added = []; const deleted = []; const untracked = []; - + statusOutput.split('\n').forEach(line => { if (!line.trim()) return; - + const status = line.substring(0, 2); const file = line.substring(3); - + if (status === 'M ' || status === ' M' || status === 'MM') { modified.push(file); } else if (status === 'A ' || status === 'AM') { @@ -118,9 +131,10 @@ router.get('/status', async (req, res) => { untracked.push(file); } }); - + res.json({ - branch: branch.trim(), + branch, + hasCommits, modified, added, deleted, @@ -128,9 +142,9 @@ router.get('/status', async (req, res) => { }); } catch (error) { console.error('Git status error:', error); - res.json({ - error: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository') - ? error.message + res.json({ + error: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository') + ? error.message : 'Git operation failed', details: error.message.includes('not a git repository') || error.message.includes('Project directory is not a git repository') ? 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 router.post('/commit', async (req, res) => { const { project, message, files } = req.body; diff --git a/src/components/GitPanel.jsx b/src/components/GitPanel.jsx index 3941091..3abd4c5 100644 --- a/src/components/GitPanel.jsx +++ b/src/components/GitPanel.jsx @@ -32,6 +32,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) { const [isPublishing, setIsPublishing] = useState(false); 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 [isCreatingInitialCommit, setIsCreatingInitialCommit] = useState(false); const textareaRef = useRef(null); const dropdownRef = useRef(null); @@ -547,7 +548,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) { const handleCommit = async () => { if (!commitMessage.trim() || selectedFiles.size === 0) return; - + setIsCommitting(true); try { const response = await authenticatedFetch('/api/git/commit', { @@ -559,7 +560,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) { files: Array.from(selectedFiles) }) }); - + const data = await response.json(); if (data.success) { // 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) => { switch (status) { @@ -1161,6 +1188,31 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
+ ) : gitStatus?.hasCommits === false ? ( +
+ +

No commits yet

+

+ This repository doesn't have any commits yet. Create your first commit to start tracking changes. +

+ +
) : !gitStatus || (!gitStatus.modified?.length && !gitStatus.added?.length && !gitStatus.deleted?.length && !gitStatus.untracked?.length) ? (