feat: add safe Gitea sync and signed updates
This commit is contained in:
@@ -5,7 +5,7 @@ function createMockRepositoryBridge(context) {
|
||||
await wait(80);
|
||||
snapshot();
|
||||
return {
|
||||
appVersion: "0.10.12-demo",
|
||||
appVersion: "0.10.13-demo",
|
||||
platform: "win32",
|
||||
state: clone(state),
|
||||
git: { available: true, version: "git version 2.47.3" },
|
||||
@@ -427,6 +427,71 @@ function createMockRepositoryBridge(context) {
|
||||
emitRepositories();
|
||||
return { output: "Fast-forwarded.", status: clone(repo.localStatus) };
|
||||
},
|
||||
async previewWorkspaceSync(localPath) {
|
||||
await wait(260);
|
||||
const repo = findRepo(localPath);
|
||||
const status = repo.localStatus;
|
||||
const targetSha = status.branch.behind ? "f".repeat(40) : status.head;
|
||||
return {
|
||||
id: `demo-${String(status.head).slice(0, 7)}-${status.branch.ahead}-${status.branch.behind}`.padEnd(64, "0").slice(0, 64),
|
||||
branch: status.branch.head,
|
||||
upstream: status.branch.upstream || `origin/${status.branch.head}`,
|
||||
currentSha: status.head,
|
||||
targetSha,
|
||||
needsSync: !status.clean || status.head !== targetSha || status.branch.ahead > 0,
|
||||
blockers: [],
|
||||
summary: {
|
||||
resultingTrackedChanges: status.branch.behind ? 3 : 0,
|
||||
added: status.branch.behind ? 1 : 0,
|
||||
modified: status.branch.behind ? 1 : 0,
|
||||
deleted: status.branch.behind ? 1 : 0,
|
||||
renamed: 0,
|
||||
localFilesToStash: status.counts.changed,
|
||||
untrackedFilesToStash: status.counts.untracked,
|
||||
localCommitsToProtect: status.branch.ahead,
|
||||
incomingCommits: status.branch.behind,
|
||||
},
|
||||
changes: status.branch.behind
|
||||
? [
|
||||
{ code: "A", status: "added", path: "src/remote-feature.js" },
|
||||
{ code: "M", status: "modified", path: "README.md" },
|
||||
{ code: "D", status: "deleted", path: "docs/obsolete.md" },
|
||||
]
|
||||
: [],
|
||||
localFiles: clone(status.files),
|
||||
incomingCommits: [],
|
||||
localCommits: [],
|
||||
recovery: {
|
||||
safetyBranch: status.branch.ahead > 0,
|
||||
stash: status.counts.changed > 0,
|
||||
untrackedCleanup: status.counts.untracked > 0,
|
||||
ignoredFilesPreserved: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
async applyWorkspaceSync(localPath, expectedPlanId) {
|
||||
const plan = await this.previewWorkspaceSync(localPath);
|
||||
if (plan.id !== expectedPlanId) throw new Error("The workspace sync preview is stale.");
|
||||
const repo = findRepo(localPath);
|
||||
const hadChanges = repo.localStatus.counts.changed > 0;
|
||||
repo.localStatus.head = plan.targetSha;
|
||||
repo.localStatus.shortHead = plan.targetSha.slice(0, 7);
|
||||
repo.localStatus.files = [];
|
||||
repo.localStatus.branch.ahead = 0;
|
||||
repo.localStatus.branch.behind = 0;
|
||||
recompute(repo);
|
||||
emitRepositories();
|
||||
return {
|
||||
applied: plan.needsSync,
|
||||
unchanged: !plan.needsSync,
|
||||
plan,
|
||||
status: clone(repo.localStatus),
|
||||
backupBranch: plan.summary.localCommitsToProtect ? `forgeflow/recovery-${plan.branch}-demo` : null,
|
||||
stash: hadChanges ? { ref: "stash@{0}", shortSha: "demo123", subject: "ForgeFlow workspace sync" } : null,
|
||||
ignoredFilesPreserved: true,
|
||||
cleaned: [],
|
||||
};
|
||||
},
|
||||
async history() {
|
||||
await wait(100);
|
||||
return clone(commitHistory);
|
||||
@@ -556,6 +621,85 @@ function createMockRepositoryBridge(context) {
|
||||
stashes: clone(list),
|
||||
};
|
||||
},
|
||||
async gitRecoveryStatus(localPath) {
|
||||
const repo = findRepo(localPath);
|
||||
const status = clone(repo.localStatus);
|
||||
const upstream = status.branch?.upstream;
|
||||
const recommendations = [
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Fetch and recalculate remote state",
|
||||
action: "fetch",
|
||||
safe: true,
|
||||
},
|
||||
];
|
||||
if (
|
||||
status.clean &&
|
||||
status.branch.behind > 0 &&
|
||||
status.branch.ahead === 0 &&
|
||||
upstream
|
||||
) {
|
||||
recommendations.push({
|
||||
id: "pull",
|
||||
label: `Fast-forward from ${upstream}`,
|
||||
action: "fast-forward",
|
||||
safe: true,
|
||||
});
|
||||
}
|
||||
if (
|
||||
status.branch.ahead > 0 &&
|
||||
status.branch.behind === 0 &&
|
||||
upstream
|
||||
) {
|
||||
recommendations.push({
|
||||
id: "push",
|
||||
label: `Push ${status.branch.ahead} local commit(s)`,
|
||||
action: "push",
|
||||
safe: true,
|
||||
});
|
||||
}
|
||||
return {
|
||||
status,
|
||||
lockReport: {
|
||||
root: localPath,
|
||||
gitDir: `${localPath}\\.git`,
|
||||
locks: [],
|
||||
processes: { available: true, active: [] },
|
||||
},
|
||||
recommendations,
|
||||
};
|
||||
},
|
||||
async reconcileRepository(localPath) {
|
||||
await wait(160);
|
||||
return this.gitRecoveryStatus(localPath);
|
||||
},
|
||||
async repairGitLocks(localPath) {
|
||||
return {
|
||||
...(await this.gitRecoveryStatus(localPath)).lockReport,
|
||||
removed: [],
|
||||
skipped: [],
|
||||
repaired: false,
|
||||
};
|
||||
},
|
||||
async repairRepositorySync(localPath, strategy) {
|
||||
const repo = findRepo(localPath);
|
||||
if (strategy === "fast-forward") {
|
||||
repo.localStatus.branch.behind = 0;
|
||||
repo.localStatus.head = "f".repeat(40);
|
||||
} else if (strategy === "push") {
|
||||
repo.localStatus.branch.ahead = 0;
|
||||
} else if (strategy !== "fetch") {
|
||||
throw new Error("Unsupported demo synchronization strategy.");
|
||||
}
|
||||
recompute(repo);
|
||||
emitRepositories();
|
||||
return {
|
||||
strategy,
|
||||
backupBranch: null,
|
||||
status: clone(repo.localStatus),
|
||||
lockReport: (await this.gitRecoveryStatus(localPath)).lockReport,
|
||||
};
|
||||
},
|
||||
async indexLockInfo() {
|
||||
return { exists: false, ageMs: 0 };
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user