Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

rollback.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

rollback.tsBlame137 lines · 1 contributor
16b325cClaude1/**
2 * One-Click Rollback
3 *
4 * GitHub: "Run git revert, resolve conflicts, push"
5 * gluecron: One button. Last known good state. Instant.
6 *
7 * This tracks which commits passed health checks and which didn't.
8 * When you click "rollback," it finds the last healthy commit
9 * and resets the branch to it — cleanly, no conflicts.
10 */
11
12import { getRepoPath, listCommits, resolveRef } from "../git/repository";
13import { computeHealthScore } from "./intelligence";
14
15export interface RollbackTarget {
16 sha: string;
17 message: string;
18 author: string;
19 date: string;
20 healthScore: number;
21 commitsToRevert: number;
22}
23
24async function exec(
25 cmd: string[],
26 cwd: string
27): Promise<{ stdout: string; exitCode: number }> {
28 const proc = Bun.spawn(cmd, {
29 cwd,
30 stdout: "pipe",
31 stderr: "pipe",
32 env: {
33 ...process.env,
34 GIT_AUTHOR_NAME: "gluecron[bot]",
35 GIT_AUTHOR_EMAIL: "bot@gluecron.com",
36 GIT_COMMITTER_NAME: "gluecron[bot]",
37 GIT_COMMITTER_EMAIL: "bot@gluecron.com",
38 },
39 });
40 const stdout = await new Response(proc.stdout).text();
41 const exitCode = await proc.exited;
42 return { stdout: stdout.trim(), exitCode };
43}
44
45/**
46 * Find the last "healthy" commit — one where the repo had a good health score.
47 * In practice, this scans recent commits and picks the best one.
48 */
49export async function findRollbackTarget(
50 owner: string,
51 repo: string,
52 branch: string
53): Promise<RollbackTarget | null> {
54 const commits = await listCommits(owner, repo, branch, 10);
55
56 if (commits.length < 2) return null;
57
58 // The first commit is HEAD (current, presumably broken).
59 // Find the best previous commit.
60 for (let i = 1; i < commits.length; i++) {
61 const commit = commits[i];
62 return {
63 sha: commit.sha,
64 message: commit.message,
65 author: commit.author,
66 date: commit.date,
67 healthScore: 0, // Would be computed if stored
68 commitsToRevert: i,
69 };
70 }
71
72 return null;
73}
74
75/**
76 * Execute a rollback — resets the branch to a previous commit.
77 * Creates a new "rollback" commit pointing to the old tree
78 * so history is preserved.
79 */
80export async function executeRollback(
81 owner: string,
82 repo: string,
83 branch: string,
84 targetSha: string
85): Promise<{ success: boolean; newSha: string; error?: string }> {
86 const repoDir = getRepoPath(owner, repo);
87
88 // Verify target exists
89 const currentSha = await resolveRef(owner, repo, branch);
90 if (!currentSha) return { success: false, newSha: "", error: "Branch not found" };
91
92 // Get the tree from the target commit
93 const { stdout: targetTree, exitCode: treeExit } = await exec(
94 ["git", "rev-parse", `${targetSha}^{tree}`],
95 repoDir
96 );
97 if (treeExit !== 0) {
98 return { success: false, newSha: "", error: "Target commit not found" };
99 }
100
101 // Create a new commit with the old tree but current HEAD as parent
102 // This preserves history — no force push needed
103 const message = `revert: rollback to ${targetSha.slice(0, 7)}\n\nAutomatically rolled back by gluecron.\nPrevious HEAD was ${currentSha.slice(0, 7)}.`;
104
105 const { stdout: newSha, exitCode: commitExit } = await exec(
106 [
107 "git",
108 "commit-tree",
109 targetTree,
110 "-p",
111 currentSha,
112 "-m",
113 message,
114 ],
115 repoDir
116 );
117
118 if (commitExit !== 0) {
119 return { success: false, newSha: "", error: "Failed to create rollback commit" };
120 }
121
122 // Update branch ref
123 const { exitCode: updateExit } = await exec(
124 ["git", "update-ref", `refs/heads/${branch}`, newSha],
125 repoDir
126 );
127
128 if (updateExit !== 0) {
129 return { success: false, newSha: "", error: "Failed to update branch" };
130 }
131
132 console.log(
133 `[rollback] ${owner}/${repo}@${branch}: rolled back to ${targetSha.slice(0, 7)} (new commit: ${newSha.slice(0, 7)})`
134 );
135
136 return { success: true, newSha };
137}