- Enhanced Git class to include method for fetching incoming changes from remote repository. - Implemented logic to retrieve and display incoming commits in the changes page. - Updated API routes to handle incoming changes and pull requests. - Modified UI components to show incoming changes and allow users to pull updates. - Improved actions bar to disable commit actions when there are incoming changes. - Added sync button to refresh repository status and check for updates.
7.9 KiB
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:
- User receives notification "Updates available for X"
- User has no way to see what those updates contain
- 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):
- Navigate to
/databases/[id]/changes - See "Incoming Changes" section with commits behind
- Review the changes (files modified in each commit)
- Click "Pull Updates" to sync
For developers (with PAT):
- Same as above, plus...
- See "Outgoing Changes" section with uncommitted ops
- Full commit/push/discard functionality
Implementation Plan
1. Remove PAT Requirement for Page Access
File: src/routes/databases/[id]/changes/+page.server.ts
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:
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
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<IncomingChanges> {
// 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
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
<script lang="ts">
export let data: PageData;
let incomingChanges: IncomingChanges | null = null;
// ... existing state ...
$: isDeveloper = data.isDeveloper;
</script>
<!-- Incoming Changes Section (visible to everyone) -->
<section>
<h2>Incoming Changes</h2>
{#if incomingChanges?.hasUpdates}
<p>{incomingChanges.commitsBehind} commits available</p>
<!-- Expandable table showing commits -->
<ExpandableTable data={incomingChanges.commits} ...>
<!-- Show commit message, author, date -->
<!-- Expanded: show files changed -->
</ExpandableTable>
<Button on:click={handlePull}>Pull Updates</Button>
{:else}
<p>Up to date</p>
{/if}
</section>
<!-- Outgoing Changes Section (developers only) -->
{#if isDeveloper}
<section>
<h2>Outgoing Changes</h2>
<!-- Existing uncommitted ops table -->
<!-- Existing commit message + push UI -->
</section>
{/if}
Sync Job Integration
When the sync job runs with auto_pull = 0:
checkForUpdates()already fetches and counts commits behind- This data is already available via
git.status()(thebehindfield) - The
/api/databases/[id]/changesendpoint 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 actionsrc/routes/databases/[id]/changes/+page.svelte- Add incoming changes UIsrc/routes/api/databases/[id]/changes/+server.ts- Remove PAT requirement for GET, add incoming changes datasrc/lib/server/utils/git/status.ts- AddgetIncomingChanges()functionsrc/lib/server/utils/git/types.ts- AddIncomingChangestypessrc/lib/server/utils/git/Git.ts- ExposegetIncomingChanges()
New Components (optional)
src/routes/databases/[id]/changes/components/IncomingChangesTable.sveltesrc/routes/databases/[id]/changes/components/OutgoingChangesTable.svelte
Could extract existing table into OutgoingChangesTable and create matching
IncomingChangesTable for consistency.
Edge Cases
- No incoming changes: Show "Up to date" message
- Pull fails: Show error, allow retry
- Conflicts: Shouldn't happen since user_ops are gitignored, but handle gracefully if it does
- 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