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:
Sam Chau
2026-01-17 15:25:24 +10:30
parent b13ec91e32
commit 47ba9dd7e9
10 changed files with 832 additions and 224 deletions

View File

@@ -2,7 +2,7 @@
* Git class - wraps git operations for a repository
*/
import type { GitStatus, OperationFile, CommitResult, UpdateInfo, Commit } from './types.ts';
import type { GitStatus, OperationFile, CommitResult, UpdateInfo, Commit, IncomingChanges } from './types.ts';
import * as repo from './repo.ts';
import * as status from './status.ts';
import * as ops from './ops.ts';
@@ -22,6 +22,7 @@ export class Git {
getBranches = () => status.getBranches(this.repoPath);
status = (options?: status.GetStatusOptions): Promise<GitStatus> => status.getStatus(this.repoPath, options);
checkForUpdates = (): Promise<UpdateInfo> => status.checkForUpdates(this.repoPath);
getIncomingChanges = (): Promise<IncomingChanges> => status.getIncomingChanges(this.repoPath);
getLastPushed = () => status.getLastPushed(this.repoPath);
getCommits = (limit?: number): Promise<Commit[]> => status.getCommits(this.repoPath, limit);
getDiff = (filepaths?: string[]): Promise<string> => status.getDiff(this.repoPath, filepaths);

View File

@@ -4,7 +4,7 @@
import { execGit, execGitSafe } from './exec.ts';
import { fetch } from './repo.ts';
import type { GitStatus, UpdateInfo, Commit } from './types.ts';
import type { GitStatus, UpdateInfo, Commit, IncomingChanges } from './types.ts';
/**
* Get current branch name
@@ -241,3 +241,58 @@ export async function getCommits(repoPath: string, limit: number = 50): Promise<
return commits;
}
/**
* Get incoming changes (commits available to pull from remote)
*/
export async function getIncomingChanges(repoPath: string): Promise<IncomingChanges> {
await 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 format = '%H|%h|%s|%an|%ae|%cI';
const output = await execGit(
['log', `--format=${format}`, `HEAD..${remoteBranch}`],
repoPath
);
const commits: Commit[] = [];
for (const line of output.split('\n')) {
if (!line.trim()) continue;
const [hash, shortHash, message, author, authorEmail, date] = line.split('|');
// Get files changed for this commit
const filesOutput = await execGitSafe(
['diff-tree', '--no-commit-id', '--name-only', '-r', hash],
repoPath
);
const files = filesOutput ? filesOutput.split('\n').filter((f) => f.trim()) : [];
commits.push({
hash,
shortHash,
message,
author,
authorEmail,
date,
files
});
}
return { hasUpdates: true, commitsBehind, commits };
}

View File

@@ -55,3 +55,9 @@ export interface Commit {
date: string;
files: string[];
}
export interface IncomingChanges {
hasUpdates: boolean;
commitsBehind: number;
commits: Commit[];
}