# Manual Pull Handling for Databases **Status: Complete** ## Summary When `auto_pull = 0`, users receive notifications that updates are available but have no way to review or pull them. This document plans extending the existing `/databases/[id]/changes` page to support incoming changes (pull) alongside outgoing changes (push). --- ## Current State ### The `/databases/[id]/changes` Page Currently this page: - Only accessible to developers (requires `personal_access_token`) - Shows **outgoing changes** (uncommitted local ops) - Allows: select files, write commit message, push to remote - Allows: discard local changes - Allows: switch branches ### The Gap When `auto_pull = 0` and updates are found: 1. User receives notification "Updates available for X" 2. User has no way to see what those updates contain 3. User has no way to pull them from the UI --- ## Proposed Solution Extend the changes page to show both directions: | Section | Who Sees It | Description | | ----------------- | ----------- | ---------------------------------- | | Incoming Changes | Everyone | Commits available to pull | | Outgoing Changes | Developers | Uncommitted local ops to push | ### User Flow **For regular users (no PAT):** 1. Navigate to `/databases/[id]/changes` 2. See "Incoming Changes" section with commits behind 3. Review the changes (files modified in each commit) 4. Click "Pull Updates" to sync **For developers (with PAT):** 1. Same as above, plus... 2. See "Outgoing Changes" section with uncommitted ops 3. Full commit/push/discard functionality --- ## Implementation Plan ### 1. Remove PAT Requirement for Page Access **File:** `src/routes/databases/[id]/changes/+page.server.ts` ```typescript export const load: PageServerLoad = async ({ parent }) => { const { database } = await parent(); // Remove the PAT check - page is now accessible to everyone // PAT only needed for push actions return { isDeveloper: !!database.personal_access_token }; }; ``` ### 2. Update API to Return Incoming Changes **File:** `src/routes/api/databases/[id]/changes/+server.ts` Remove PAT requirement for GET. Add incoming changes data: ```typescript export const GET: RequestHandler = async ({ params }) => { const database = databaseInstancesQueries.getById(id); const git = new Git(database.local_path); // Fetch for everyone const [status, incomingChanges, branches, repoInfo] = await Promise.all([ git.status(), git.getIncomingChanges(), git.getBranches(), getRepoInfo(database.repository_url, database.personal_access_token) ]); // Only fetch outgoing changes for developers let uncommittedOps = null; if (database.personal_access_token) { uncommittedOps = await git.getUncommittedOps(); } return json({ status, incomingChanges, branches, repoInfo, uncommittedOps }); }; ``` ### 3. Add Git Function for Incoming Changes **File:** `src/lib/server/utils/git/status.ts` ```typescript export interface IncomingCommit { hash: string; shortHash: string; message: string; author: string; date: string; files: string[]; } export interface IncomingChanges { hasUpdates: boolean; commitsBehind: number; commits: IncomingCommit[]; } export async function getIncomingChanges(repoPath: string): Promise { // Fetch latest from remote await execGitSafe(['fetch'], repoPath); const branch = await getBranch(repoPath); const remoteBranch = `origin/${branch}`; // Count commits behind const countOutput = await execGitSafe( ['rev-list', '--count', `HEAD..${remoteBranch}`], repoPath ); const commitsBehind = parseInt(countOutput || '0') || 0; if (commitsBehind === 0) { return { hasUpdates: false, commitsBehind: 0, commits: [] }; } // Get commit details for incoming commits const logOutput = await execGitSafe( ['log', '--format=%H|%h|%s|%an|%aI', `HEAD..${remoteBranch}`], repoPath ); const commits: IncomingCommit[] = []; for (const line of logOutput.split('\n').filter(Boolean)) { const [hash, shortHash, message, author, date] = line.split('|'); // Get files changed in this commit const filesOutput = await execGitSafe( ['diff-tree', '--no-commit-id', '--name-only', '-r', hash], repoPath ); const files = filesOutput.split('\n').filter(Boolean); commits.push({ hash, shortHash, message, author, date, files }); } return { hasUpdates: true, commitsBehind, commits }; } ``` ### 4. Add Pull Action **File:** `src/routes/databases/[id]/changes/+page.server.ts` ```typescript export const actions: Actions = { // ... existing actions ... pull: async ({ params }) => { const id = parseInt(params.id || '', 10); const database = databaseInstancesQueries.getById(id); if (!database) { return { success: false, error: 'Database not found' }; } try { const result = await pcdManager.sync(id); return result; } catch (err) { return { success: false, error: err instanceof Error ? err.message : 'Failed to pull' }; } } }; ``` ### 5. Update Page UI **File:** `src/routes/databases/[id]/changes/+page.svelte` ```svelte

Incoming Changes

{#if incomingChanges?.hasUpdates}

{incomingChanges.commitsBehind} commits available

{:else}

Up to date

{/if}
{#if isDeveloper}

Outgoing Changes

{/if} ``` --- ## Sync Job Integration When the sync job runs with `auto_pull = 0`: 1. `checkForUpdates()` already fetches and counts commits behind 2. This data is already available via `git.status()` (the `behind` field) 3. The `/api/databases/[id]/changes` endpoint will show this when user visits No additional "store" needed - the git state IS the store. Each time the user visits the changes page, we fetch fresh data from git. --- ## Files to Create/Modify ### Modified Files - `src/routes/databases/[id]/changes/+page.server.ts` - Remove PAT requirement for load, add pull action - `src/routes/databases/[id]/changes/+page.svelte` - Add incoming changes UI - `src/routes/api/databases/[id]/changes/+server.ts` - Remove PAT requirement for GET, add incoming changes data - `src/lib/server/utils/git/status.ts` - Add `getIncomingChanges()` function - `src/lib/server/utils/git/types.ts` - Add `IncomingChanges` types - `src/lib/server/utils/git/Git.ts` - Expose `getIncomingChanges()` ### New Components (optional) - `src/routes/databases/[id]/changes/components/IncomingChangesTable.svelte` - `src/routes/databases/[id]/changes/components/OutgoingChangesTable.svelte` Could extract existing table into `OutgoingChangesTable` and create matching `IncomingChangesTable` for consistency. --- ## Edge Cases 1. **No incoming changes**: Show "Up to date" message 2. **Pull fails**: Show error, allow retry 3. **Conflicts**: Shouldn't happen since user_ops are gitignored, but handle gracefully if it does 4. **Large number of commits**: Paginate or limit to recent N commits --- ## UI Considerations - StatusCard (repo info, branch switcher) visible for everyone - Use consistent table styling between incoming/outgoing - Incoming table is read-only (no checkboxes) - Clear visual separation between sections - Consider showing incoming changes count in the tab/nav if updates available