Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion ui/gitRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
slugify
} from './routes.js';
import { ensureRepositoryCheckout } from './repositoryManager.js';
import { getDefaultBranch, checkExistingPR, isPRBranch } from './helpers.js';
import { getDefaultBranch, checkExistingPR, isPRBranch, isOnDefaultBranch } from './helpers.js';
import tokenManager from './tokenManager.js';

// Setup Git routes
Expand Down Expand Up @@ -625,6 +625,17 @@ export function setupGitRoutes(app, isAuthenticated, dependencies) {
const username = req.user.username;
const userRepo = getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig);

// Check if on default branch - block commits
const branchCheck = await isOnDefaultBranch(userRepo);
if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot commit changes on default branch (${branchCheck.defaultBranch}). Please create a new branch before committing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

const git = simpleGit({ baseDir: userRepo });
await ensureGitRemoteWithToken(git, req.user.token, REPO_URL);

Expand Down Expand Up @@ -794,6 +805,17 @@ export function setupGitRoutes(app, isAuthenticated, dependencies) {
return res.status(404).json({ error: 'Repository not found' });
}

// Check if on default branch - block file checkout (discarding changes)
const branchCheck = await isOnDefaultBranch(userRepo);
if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot discard file changes on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

try {
// Initialize simple-git with the user's repo path
const git = simpleGit(userRepo);
Expand Down Expand Up @@ -1094,6 +1116,17 @@ export function setupGitRoutes(app, isAuthenticated, dependencies) {
return res.status(404).json({ error: 'Repository not found' });
}

// Check if on default branch - block undo operations
const branchCheck = await isOnDefaultBranch(userRepo);
if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot undo changes on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

const git = simpleGit({ baseDir: userRepo });
const isRepo = await git.checkIsRepo();

Expand Down Expand Up @@ -1225,6 +1258,17 @@ export function setupGitRoutes(app, isAuthenticated, dependencies) {
return res.status(404).json({ error: 'Repository not found' });
}

// Check if on default branch - block redo operations
const branchCheck = await isOnDefaultBranch(userRepo);
if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot redo changes on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

const git = simpleGit({ baseDir: userRepo });
const isRepo = await git.checkIsRepo();

Expand Down
29 changes: 29 additions & 0 deletions ui/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,35 @@ export function updateSessionMetadata(session) {
}
}

/**
* Check if current branch is a default branch (main/master)
* Reuses logic from autoCommitAndPush to avoid duplication
* @param {string} userRepoPath - Path to user repository
* @returns {Promise<Object>} Object with isDefault boolean and branch names
*/
export async function isOnDefaultBranch(userRepoPath) {
try {
const git = simpleGit(userRepoPath);

const isRepo = await git.checkIsRepo();
if (!isRepo) {
return { isDefault: false, currentBranch: null, defaultBranch: null };
}

const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git.revparse() call returns a string with a trailing newline character. This needs to be trimmed before comparison with defaultBranch to avoid false negatives when checking currentBranch === defaultBranch.

Suggested change
const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
const currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();

Copilot uses AI. Check for mistakes.
const defaultBranch = await getDefaultBranch(git, userRepoPath);

return {
isDefault: currentBranch === defaultBranch,
currentBranch,
defaultBranch
};
} catch (error) {
console.error('Error checking default branch:', error);
return { isDefault: false, currentBranch: null, defaultBranch: null };
}
}

/**
* Auto-commit and push changes when not on default branch
* @param {string} userRepoPath - Path to user repository
Expand Down
54 changes: 53 additions & 1 deletion ui/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
generateRecFileToMapWasm,
validateTestFromMapWasm
} from './wasmNodeWrapper.js';
import { autoCommitAndPush } from './helpers.js';
import { autoCommitAndPush, isOnDefaultBranch } from './helpers.js';

// Helper functions that were in server.js
export function getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig) {
Expand Down Expand Up @@ -492,6 +492,19 @@ export function setupRoutes(app, isAuthenticated, dependencies) {
return res.status(400).json({ error: 'File path is required' });
}

// Check if on default branch - block file modifications
const userRepoPath = getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const branchCheck = await isOnDefaultBranch(userRepoPath);

if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot save files on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}
Comment on lines +502 to +513
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch check logic is duplicated across multiple endpoints (lines 495-506, 623-634, 669-680, 736-747 in routes.js and lines 628-637, 808-817, 1119-1128, 1261-1270 in gitRoutes.js). Consider extracting this into a reusable middleware function to reduce code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.

// Use the user's test directory as the base
const testDir = getUserTestPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const absolutePath = path.join(testDir, filePath);
Expand Down Expand Up @@ -607,6 +620,19 @@ export function setupRoutes(app, isAuthenticated, dependencies) {
return res.status(400).json({ error: 'Source and target paths are required' });
}

// Check if on default branch - block file modifications
const userRepoPath = getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const branchCheck = await isOnDefaultBranch(userRepoPath);

if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot move files on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

// Use the user's test directory as the base
const testDir = getUserTestPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const absoluteSourcePath = path.join(testDir, sourcePath);
Expand Down Expand Up @@ -640,6 +666,19 @@ export function setupRoutes(app, isAuthenticated, dependencies) {
return res.status(400).json({ error: 'File path is required' });
}

// Check if on default branch - block file modifications
const userRepoPath = getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const branchCheck = await isOnDefaultBranch(userRepoPath);

if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot delete files on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

// Use the user's test directory as the base
const testDir = getUserTestPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const absolutePath = path.join(testDir, filePath);
Expand Down Expand Up @@ -694,6 +733,19 @@ export function setupRoutes(app, isAuthenticated, dependencies) {
return res.status(400).json({ error: 'Directory path is required' });
}

// Check if on default branch - block directory creation
const userRepoPath = getUserRepoPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const branchCheck = await isOnDefaultBranch(userRepoPath);

if (branchCheck.isDefault) {
return res.status(403).json({
error: `Cannot create directories on default branch (${branchCheck.defaultBranch}). Please create a new branch before editing.`,
currentBranch: branchCheck.currentBranch,
defaultBranch: branchCheck.defaultBranch,
isDefaultBranch: true
});
}

// Use the user's test directory as the base
const testDir = getUserTestPath(req, WORKDIR, ROOT_DIR, getAuthConfig);
const absolutePath = path.join(testDir, dirPath);
Expand Down