-
Notifications
You must be signed in to change notification settings - Fork 0
Add repo picker to spawn dialog and auto-worktree on project collision #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
87b93ea
682fb80
8af5696
189c5b6
5f81145
8dc02fa
47394a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,8 @@ | ||||||||||||||||||||||||||||
| import { spawn } from 'child_process' | ||||||||||||||||||||||||||||
| import { createHash } from 'crypto' | ||||||||||||||||||||||||||||
| import { existsSync } from 'fs' | ||||||||||||||||||||||||||||
| import { existsSync, mkdirSync } from 'fs' | ||||||||||||||||||||||||||||
| import { appendFile, mkdtemp, readFile, rm, stat } from 'fs/promises' | ||||||||||||||||||||||||||||
| import { join } from 'path' | ||||||||||||||||||||||||||||
| import { dirname, join } from 'path' | ||||||||||||||||||||||||||||
| import { tmpdir } from 'os' | ||||||||||||||||||||||||||||
| import { assertDirectory } from './path-utils' | ||||||||||||||||||||||||||||
| import { cacheAvatarFromUrl, cachedAvatarUrl } from './avatar-cache' | ||||||||||||||||||||||||||||
|
|
@@ -1584,3 +1584,21 @@ export async function getSelectedDiff(path: string, input: { wholeFiles: string[ | |||||||||||||||||||||||||||
| const patch = input.patch?.trim() ? input.patch.trim() : '' | ||||||||||||||||||||||||||||
| return [...diffs, patch].filter(Boolean).join('\n') | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export async function getGitRoot(path: string): Promise<string | null> { | ||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| const result = await runGit(['rev-parse', '--show-toplevel'], path) | ||||||||||||||||||||||||||||
| return result.trim() | ||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||
| return null | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export async function createWorktree(repoPath: string, worktreePath: string, branchName: string): Promise<void> { | ||||||||||||||||||||||||||||
| mkdirSync(dirname(worktreePath), { recursive: true }) | ||||||||||||||||||||||||||||
| await runGit(['worktree', 'add', '-b', branchName, worktreePath], repoPath) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+1597
to
+1600
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the branch specified by
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export function worktreeExists(worktreePath: string): boolean { | ||||||||||||||||||||||||||||
| return existsSync(join(worktreePath, '.git')) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -842,9 +842,13 @@ export function ProjectSettings(): React.ReactNode { | |
| const activeChannelName = useProjectStore((s) => s.activeChannelName) | ||
| const setActiveRoot = useProjectStore((s) => s.setActiveRoot) | ||
| const setActiveChannel = useProjectStore((s) => s.setActiveChannel) | ||
| const setActiveProject = useProjectStore((s) => s.setActiveProject) | ||
| const updateProject = useProjectStore((s) => s.updateProject) | ||
| const removeProject = useProjectStore((s) => s.removeProject) | ||
| const addRoot = useProjectStore((s) => s.addRoot) | ||
| const clearRootConflict = useProjectStore((s) => s.clearRootConflict) | ||
| const createWorktreeRoot = useProjectStore((s) => s.createWorktreeRoot) | ||
| const pendingRootConflict = useProjectStore((s) => s.pendingRootConflict) | ||
| const removeRoot = useProjectStore((s) => s.removeRoot) | ||
| const addChannel = useProjectStore((s) => s.addChannel) | ||
| const removeChannel = useProjectStore((s) => s.removeChannel) | ||
|
|
@@ -1023,6 +1027,47 @@ export function ProjectSettings(): React.ReactNode { | |
| }} | ||
| /> | ||
| ))} | ||
| {pendingRootConflict?.projectId === project.id && ( | ||
| <div className="rounded-lg border border-[var(--pear-yellow,#f59e0b)]/30 bg-[var(--pear-yellow,#f59e0b)]/10 p-4 text-sm"> | ||
| <p className="mb-1 font-medium text-[var(--pear-text)]">Repo already in another project</p> | ||
| <p className="mb-3 text-[var(--pear-text-dim)]"> | ||
| <span className="font-mono text-xs">{pendingRootConflict.path}</span> is already part of{' '} | ||
| <strong>{pendingRootConflict.existingProjectName}</strong>. Create an isolated git worktree | ||
| so both projects can work independently, or go to the existing project. | ||
| </p> | ||
| <div className="flex flex-wrap gap-2"> | ||
| <button | ||
| type="button" | ||
| onClick={() => | ||
| void run(async () => { | ||
| const root = await createWorktreeRoot(pendingRootConflict.path) | ||
| if (root) clearRootConflict() | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| className="rounded-md bg-[var(--pear-accent)] px-3 py-1.5 text-xs font-medium text-white hover:bg-[var(--pear-accent-bright)]" | ||
| > | ||
| Create worktree | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| clearRootConflict() | ||
| void setActiveProject(pendingRootConflict.existingProjectId) | ||
|
Comment on lines
+1054
to
+1055
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Severity Level: Major
|
||
| }} | ||
| className="rounded-md border border-[var(--pear-border)] px-3 py-1.5 text-xs text-[var(--pear-text-dim)] hover:bg-[var(--pear-bg-overlay)] hover:text-[var(--pear-text)]" | ||
| > | ||
| Go to “{pendingRootConflict.existingProjectName}” | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={clearRootConflict} | ||
| className="rounded-md px-3 py-1.5 text-xs text-[var(--pear-text-faint)] hover:text-[var(--pear-text-dim)]" | ||
| > | ||
| Cancel | ||
| </button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </Section> | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid importing and using synchronous file system methods like
mkdirSyncin asynchronous functions within the Electron main process, as they can block the main thread. Instead, importmkdirfromfs/promises.