Skip to content

feat(projects): support non-git projects#1942

Open
janburzinski wants to merge 13 commits into
generalaction:mainfrom
janburzinski:emdash/non-git-repos-xlkne
Open

feat(projects): support non-git projects#1942
janburzinski wants to merge 13 commits into
generalaction:mainfrom
janburzinski:emdash/non-git-repos-xlkne

Conversation

@janburzinski
Copy link
Copy Markdown
Collaborator

summary

adding support for non git repositories

@janburzinski janburzinski marked this pull request as ready for review May 9, 2026 19:10
@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 9, 2026

Greptile Summary

This PR adds first-class support for non-git projects — folders that contain no git repository — allowing users to add, manage, and create tasks in plain directories. A new is_git_repo DB column (defaulting to 1) is persisted, and all git-specific UI surfaces (branch info, PR tab, push/pull/fetch controls, issue selector) are hidden when isGitRepo === false.

  • Core creation paths (create-local-project.ts, create-ssh-project.ts): a new noGit flag bypasses all git setup, stores is_git_repo = 0, and keeps baseRef = ''; both the add-project modal and sidebar drag-and-drop pass this flag correctly.
  • Task creation (createTask.ts): non-git projects are forced to the no-worktree strategy; lazy getConfiguredRemote avoids unnecessary network calls; sourceBranch guards are added for new-branch and checkout-existing strategy paths.
  • Renderer (repository-store.ts, project-view.ts): git data fetching and event subscriptions are skipped for non-git projects; a stale pull-request view in saved snapshots is corrected back to tasks on restore.

Confidence Score: 4/5

The feature code is well-implemented and previously flagged concerns are addressed, but the migration journal rebasing poses a real risk for users who ran the app on main before this merges.

The non-git project logic is sound end-to-end: isGitRepo is persisted correctly, git UI surfaces are consistently gated, the task creation path is properly short-circuited, and new unit tests cover the critical paths. The unresolved concern is the _journal.json rebasing: idx-9 and idx-10 were renamed/consolidated, so Drizzle may attempt to re-apply migrations already recorded against the old tags, failing with duplicate-column errors for users who upgraded through a prior build.

drizzle/meta/_journal.json and the accompanying SQL files — the rebasing of migration tags needs to be verified against Drizzle's migration tracking mechanism before merging into production.

Important Files Changed

Filename Overview
src/main/core/tasks/operations/createTask.ts Non-git projects are correctly forced to no-worktree; sourceBranch guards added for new-branch and checkout-existing; getConfiguredRemote is now lazy — clean and well-tested.
src/main/core/projects/operations/create-local-project.ts Adds noGit branch that skips git setup, stores isGitRepo=0, and uses raw params.path as resolvedPath — logic is symmetric with the SSH counterpart and correctly guards git operations.
src/main/core/projects/operations/create-ssh-project.ts Mirrors the local-project noGit path for SSH projects; path existence validation still runs before the noGit branch, so invalid paths are rejected regardless.
src/renderer/features/projects/components/add-project-modal/add-project-modal.tsx Unhandled-rejection gap (prev. thread) is now addressed with try/catch + toast. Stale pickPathStatusQuery concern (prev. thread) is largely mitigated by the fresh freshPickInspection call on submit.
src/renderer/features/projects/stores/repository-store.ts Correctly skips live git RPC calls and event subscriptions for non-git projects by returning resolved empty payloads and guarding localData.start()/remoteData.start().
src/renderer/features/tasks/create-task-modal/create-task-modal.tsx Non-git path short-circuits to NoGitContent and no-worktree task creation; git-mode hooks receive isActive=false to suppress unnecessary name-generation queries.
drizzle/meta/_journal.json Migration journal rebasing (prev. thread): idx-9 tag renamed and idx-10 removed. Risk depends on whether Drizzle tracks applied migrations by tag/filename or content hash.
src/main/db/schema.ts isGitRepo column added as NOT NULL INTEGER DEFAULT 1, correctly treating all pre-existing rows as git projects on migration.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/renderer/features/projects/components/add-project-modal/modes.ts:31-36
The exported `noGit` field is `!initGitRepository`, which is `true` by default (since `initGitRepository` starts as `false`). This means every picked path begins with `noGit = true` until the UI toggle is flipped on. In `handleSubmit` this is corrected by `freshPickInspection`, but `pickNoGit` (derived here) feeds the submit logic through two indirection layers, making the intent harder to follow. A clarifying comment would help signal that this is a derived default, not explicit user intent.

```suggestion
    path,
    name,
    initGitRepository,
    setInitGitRepository,
    /** True when the user has not opted in to git init — interpreted as "add as non-git". */
    noGit: !initGitRepository,
    handlePathChange,
```

Reviews (4): Last reviewed commit: "handle project path inspection failures" | Re-trigger Greptile

Comment on lines +395 to +412
name: 'Project',
path: projectPath,
connectionId: 'connection-id',
noGit: true,
});

expect(mocks.sshStatMock).toHaveBeenCalledWith('');
expect(mocks.detectInfoMock).not.toHaveBeenCalled();
expect(mocks.initRepositoryMock).not.toHaveBeenCalled();
expect(mocks.valuesMock).toHaveBeenCalledWith(
expect.objectContaining({ isGitRepo: 0, baseRef: '', path: projectPath })
);
expect(created).toMatchObject({
id: 'project-id',
isGitRepo: false,
type: 'ssh',
connectionId: 'connection-id',
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 SSH test assertion uses '' instead of projectPath

The test passes path: projectPath to createSshProject — which means sshProxy.stat(params.path) is called with projectPath — yet the assertion checks toHaveBeenCalledWith(''). If projectPath in this describe block is not an empty string, the test will fail in CI. Even if the test happens to pass because projectPath === '', it would be validating the wrong call site: the intent was presumably to confirm that the directory-existence check still runs for non-git SSH projects.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/core/projects/operations/createProject.test.ts
Line: 395-412

Comment:
**SSH test assertion uses `''` instead of `projectPath`**

The test passes `path: projectPath` to `createSshProject` — which means `sshProxy.stat(params.path)` is called with `projectPath` — yet the assertion checks `toHaveBeenCalledWith('')`. If `projectPath` in this describe block is not an empty string, the test will fail in CI. Even if the test happens to pass because `projectPath === ''`, it would be validating the wrong call site: the intent was presumably to confirm that the directory-existence check still runs for non-git SSH projects.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 77 to +80
case 'new-branch': {
if (!params.sourceBranch) {
return err({ type: 'branch-not-found', branch: strategy.taskBranch });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 When sourceBranch is missing for a new-branch strategy, the error type branch-not-found refers to strategy.taskBranch — the proposed new branch, not a branch that was looked up and not found. Callers handling this error would display a misleading "branch not found" message for what is actually a missing required parameter. provision-failed (used by checkout-existing in the same file) is a more accurate signal here.

Suggested change
case 'new-branch': {
if (!params.sourceBranch) {
return err({ type: 'branch-not-found', branch: strategy.taskBranch });
}
case 'new-branch': {
if (!params.sourceBranch) {
return err({
type: 'provision-failed',
message: 'Cannot create a new branch without a source branch.',
});
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/core/tasks/operations/createTask.ts
Line: 77-80

Comment:
When `sourceBranch` is missing for a `new-branch` strategy, the error type `branch-not-found` refers to `strategy.taskBranch` — the *proposed new branch*, not a branch that was looked up and not found. Callers handling this error would display a misleading "branch not found" message for what is actually a missing required parameter. `provision-failed` (used by `checkout-existing` in the same file) is a more accurate signal here.

```suggestion
    case 'new-branch': {
      if (!params.sourceBranch) {
        return err({
          type: 'provision-failed',
          message: 'Cannot create a new branch without a source branch.',
        });
      }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread drizzle/meta/_journal.json Outdated
Comment on lines 62 to 82
"idx": 8,
"version": "6",
"when": 1778075084353,
"tag": "0008_past_shinko_yamashiro",
"when": 1778009870486,
"tag": "0008_bouncy_tyger_tiger",
"breakpoints": true
},
{
"idx": 9,
"version": "6",
"when": 1778239261398,
"tag": "0009_lean_goblin_queen",
"when": 1778136581718,
"tag": "0009_new_exiles",
"breakpoints": true
},
{
"idx": 10,
"version": "6",
"when": 1778310618633,
"tag": "0010_hot_sebastian_shaw",
"breakpoints": true
},
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Migration journal rebasing may break existing installs

The journal renames the two migrations that exist on main (0008_past_shinko_yamashiro0009_new_exiles, 0009_lean_goblin_queen0010_hot_sebastian_shaw) and inserts 0008_bouncy_tyger_tiger ahead of them. Whether this is safe depends on how Drizzle tracks applied migrations at runtime: if it uses content hashes the renamed entries will be correctly recognised as already-applied, but if it tracks by tag/filename a user who ran the app on main would see 0009_new_exiles and 0010_hot_sebastian_shaw treated as new migrations and fail with "duplicate column" errors on last_interacted_at and is_initial_conversation. Please verify the tracking mechanism before merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: drizzle/meta/_journal.json
Line: 62-82

Comment:
**Migration journal rebasing may break existing installs**

The journal renames the two migrations that exist on `main` (`0008_past_shinko_yamashiro``0009_new_exiles`, `0009_lean_goblin_queen``0010_hot_sebastian_shaw`) and inserts `0008_bouncy_tyger_tiger` ahead of them. Whether this is safe depends on how Drizzle tracks applied migrations at runtime: if it uses content hashes the renamed entries will be correctly recognised as already-applied, but if it tracks by tag/filename a user who ran the app on `main` would see `0009_new_exiles` and `0010_hot_sebastian_shaw` treated as new migrations and fail with "duplicate column" errors on `last_interacted_at` and `is_initial_conversation`. Please verify the tracking mechanism before merging.

How can I resolve this? If you propose a fix, please make it concise.

@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment on lines +146 to 147
const pickNoGit = pickPathStatusQuery.data?.isGitRepo === true ? false : pickState.noGit;
const isCheckingPickPathStatus = shouldCheckPickPathStatus && pickPathStatusQuery.isPending;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Stale path-status data used when determining noGit

pickNoGit is computed from a cached query result: pickPathStatusQuery.data?.isGitRepo === true ? false : pickState.noGit. If the query finishes in error state (e.g. SSH connection drops between path input and submit), pickPathStatusQuery.data is undefined, so pickNoGit = pickState.noGit = !initGitRepository = true. A directory that is actually a git repo will be registered with is_git_repo = 0, permanently treating it as non-git until the user deletes and re-adds the project. The server-side createProject call does a fresh stat but does not re-validate the git status when noGit is explicitly true, so the stale flag propagates to the DB.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/features/projects/components/add-project-modal/add-project-modal.tsx
Line: 146-147

Comment:
**Stale path-status data used when determining `noGit`**

`pickNoGit` is computed from a cached query result: `pickPathStatusQuery.data?.isGitRepo === true ? false : pickState.noGit`. If the query finishes in error state (e.g. SSH connection drops between path input and submit), `pickPathStatusQuery.data` is `undefined`, so `pickNoGit = pickState.noGit = !initGitRepository = true`. A directory that is actually a git repo will be registered with `is_git_repo = 0`, permanently treating it as non-git until the user deletes and re-adds the project. The server-side `createProject` call does a fresh stat but does not re-validate the git status when `noGit` is explicitly `true`, so the stale flag propagates to the DB.

How can I resolve this? If you propose a fix, please make it concise.

@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment on lines +153 to +161
const handleSubmit = async () => {
try {
const inspection = await rpc.projects.inspectProjectPath(
strategy === 'ssh'
? { type: 'ssh', path: pickState.path, connectionId: selectedConnectionId! }
: { type: 'local', path: pickState.path }
);
if (inspection.existingProject) {
navigate('project', { projectId: inspection.existingProject.id });
onClose();
return;
}
} catch (e) {
log.error(e);
const freshPickInspection =
mode === 'pick'
? await rpc.projects.inspectProjectPath(
strategy === 'ssh'
? { type: 'ssh', path: pickState.path, connectionId: selectedConnectionId! }
: { type: 'local', path: pickState.path }
)
: undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unhandled rejection if inspection throws during submit

The old handleSubmit wrapped inspectProjectPath in a try/catch and fell through to project creation on error. The new code awaits the call without any error handling — if the RPC fails (e.g. path deleted between validation and submit, SSH connection drops), handleSubmit throws an unhandled promise rejection. The user sees no error message and the project is never created, leaving the modal open with no indication of what went wrong. Wrapping the await in a try/catch and surfacing a toast on failure would restore the previous resilience.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/features/projects/components/add-project-modal/add-project-modal.tsx
Line: 153-161

Comment:
**Unhandled rejection if inspection throws during submit**

The old `handleSubmit` wrapped `inspectProjectPath` in a `try/catch` and fell through to project creation on error. The new code awaits the call without any error handling — if the RPC fails (e.g. path deleted between validation and submit, SSH connection drops), `handleSubmit` throws an unhandled promise rejection. The user sees no error message and the project is never created, leaving the modal open with no indication of what went wrong. Wrapping the `await` in a try/catch and surfacing a toast on failure would restore the previous resilience.

How can I resolve this? If you propose a fix, please make it concise.

@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant