mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 19:01:02 +01:00
feat: manual incoming changes handling
- 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.
This commit is contained in:
302
docs/todo/2.manual-pull-handling.md
Normal file
302
docs/todo/2.manual-pull-handling.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 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<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`
|
||||
|
||||
```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
|
||||
<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`:
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user