refactor: simplify worktree management by removing legacy API
- Remove legacy worktree API usage and related state - Add Manage Branches button in the Git header for quick access - Introduce worktree status utilities to derive root branch hints
This commit is contained in:
@@ -891,24 +891,6 @@ const sanitizeProjects = (input) => {
|
||||
...(Number.isFinite(lastOpenedAt) && lastOpenedAt >= 0 ? { lastOpenedAt } : {}),
|
||||
};
|
||||
|
||||
// Preserve worktreeDefaults
|
||||
if (candidate.worktreeDefaults && typeof candidate.worktreeDefaults === 'object') {
|
||||
const wt = candidate.worktreeDefaults;
|
||||
const defaults = {};
|
||||
if (typeof wt.branchPrefix === 'string' && wt.branchPrefix.trim()) {
|
||||
defaults.branchPrefix = wt.branchPrefix.trim();
|
||||
}
|
||||
if (typeof wt.baseBranch === 'string' && wt.baseBranch.trim()) {
|
||||
defaults.baseBranch = wt.baseBranch.trim();
|
||||
}
|
||||
if (typeof wt.autoCreateWorktree === 'boolean') {
|
||||
defaults.autoCreateWorktree = wt.autoCreateWorktree;
|
||||
}
|
||||
if (Object.keys(defaults).length > 0) {
|
||||
project.worktreeDefaults = defaults;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof candidate.sidebarCollapsed === 'boolean') {
|
||||
project.sidebarCollapsed = candidate.sidebarCollapsed;
|
||||
}
|
||||
@@ -7111,65 +7093,6 @@ Context:
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/git/worktrees', async (req, res) => {
|
||||
const { addWorktree } = await getGitLibraries();
|
||||
try {
|
||||
const directory = req.query.directory;
|
||||
if (!directory) {
|
||||
return res.status(400).json({ error: 'directory parameter is required' });
|
||||
}
|
||||
|
||||
const { path, branch, createBranch, startPoint } = req.body;
|
||||
if (!path || !branch) {
|
||||
return res.status(400).json({ error: 'path and branch are required' });
|
||||
}
|
||||
|
||||
const result = await addWorktree(directory, path, branch, { createBranch, startPoint });
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Failed to add worktree:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to add worktree' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/git/worktrees', async (req, res) => {
|
||||
const { removeWorktree } = await getGitLibraries();
|
||||
try {
|
||||
const directory = req.query.directory;
|
||||
if (!directory) {
|
||||
return res.status(400).json({ error: 'directory parameter is required' });
|
||||
}
|
||||
|
||||
const { path, force } = req.body;
|
||||
if (!path) {
|
||||
return res.status(400).json({ error: 'path is required' });
|
||||
}
|
||||
|
||||
const result = await removeWorktree(directory, path, { force });
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Failed to remove worktree:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to remove worktree' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/git/ignore-openchamber', async (req, res) => {
|
||||
// LEGACY_WORKTREES: only needed for <project>/.openchamber era. Safe to remove after legacy support dropped.
|
||||
const { ensureOpenChamberIgnored } = await getGitLibraries();
|
||||
try {
|
||||
const directory = req.query.directory;
|
||||
if (!directory) {
|
||||
return res.status(400).json({ error: 'directory parameter is required' });
|
||||
}
|
||||
|
||||
await ensureOpenChamberIgnored(directory);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to ignore .openchamber directory:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to update git ignore' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/git/worktree-type', async (req, res) => {
|
||||
const { isLinkedWorktree } = await getGitLibraries();
|
||||
try {
|
||||
|
||||
@@ -130,46 +130,6 @@ export async function isGitRepository(directory) {
|
||||
return fs.existsSync(gitDir);
|
||||
}
|
||||
|
||||
export async function ensureOpenChamberIgnored(directory) {
|
||||
// LEGACY_WORKTREES: only needed for <project>/.openchamber era. Safe to remove after legacy support dropped.
|
||||
const directoryPath = normalizeDirectoryPath(directory);
|
||||
if (!directoryPath || !fs.existsSync(directoryPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gitDir = path.join(directoryPath, '.git');
|
||||
if (!fs.existsSync(gitDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const infoDir = path.join(gitDir, 'info');
|
||||
const excludePath = path.join(infoDir, 'exclude');
|
||||
const entry = '/.openchamber/';
|
||||
|
||||
try {
|
||||
await fsp.mkdir(infoDir, { recursive: true });
|
||||
let contents = '';
|
||||
try {
|
||||
contents = await fsp.readFile(excludePath, 'utf8');
|
||||
} catch (readError) {
|
||||
if (readError && readError.code !== 'ENOENT') {
|
||||
throw readError;
|
||||
}
|
||||
}
|
||||
|
||||
const lines = contents.split(/\r?\n/).map((line) => line.trim());
|
||||
if (!lines.includes(entry)) {
|
||||
const prefix = contents.length > 0 && !contents.endsWith('\n') ? '\n' : '';
|
||||
await fsp.appendFile(excludePath, `${prefix}${entry}\n`, 'utf8');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to ensure .openchamber ignore:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGlobalIdentity() {
|
||||
const git = await createGit();
|
||||
|
||||
@@ -1018,63 +978,6 @@ export async function getWorktrees(directory) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function addWorktree(directory, worktreePath, branch, options = {}) {
|
||||
const git = await createGit(directory);
|
||||
|
||||
try {
|
||||
const args = ['worktree', 'add'];
|
||||
const startPoint = typeof options.startPoint === 'string' ? options.startPoint.trim() : '';
|
||||
|
||||
if (options.createBranch) {
|
||||
args.push('-b', branch);
|
||||
}
|
||||
|
||||
args.push(worktreePath);
|
||||
|
||||
if (!options.createBranch) {
|
||||
args.push(branch);
|
||||
} else if (startPoint) {
|
||||
args.push(startPoint);
|
||||
}
|
||||
|
||||
await git.raw(args);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: worktreePath,
|
||||
branch
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to add worktree:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeWorktree(directory, worktreePath, options = {}) {
|
||||
const git = await createGit(directory);
|
||||
|
||||
try {
|
||||
const args = ['worktree', 'remove', worktreePath];
|
||||
|
||||
if (options.force) {
|
||||
args.push('--force');
|
||||
}
|
||||
|
||||
await git.raw(args);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
// If the worktree doesn't exist or isn't recognized by git, treat as success
|
||||
// since the goal (removing the worktree) is already achieved.
|
||||
const errorMessage = String(error?.message || error || '');
|
||||
if (errorMessage.includes('is not a working tree') || errorMessage.includes('is not a valid path')) {
|
||||
return { success: true };
|
||||
}
|
||||
console.error('Failed to remove worktree:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteBranch(directory, branch, options = {}) {
|
||||
const git = await createGit(directory);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user