ReviewOS

also looking at this

stacks/buddy

fix(ci): land the lock-file repair instead of describing it

#1408
Merged glennmichael123 wants to merge fix/persist-the-mechanical-lockfile-repair into main
6 files +193 -15
src/utils/git.tsmodified+40-0
Changes to src/utils/git.ts
@@ -51,3 +51,43 @@ export async function hasBranchDifferences(fileUpdates: SimpleFileUpdate[], bran
5151 }
5252 return false
5353}
54
55/**
56 * Commit the given paths and push them to the current branch.
57 *
58 * Used by the mechanical CI repair, which runs in a job that has already
59 * checked the failing branch out with credentials. Deliberately not
60 * `GitProvider.commitChanges`: that recreates a branch from its base, which is
61 * right for a dependency update Buddy owns end to end and catastrophic here,
62 * where the branch carries someone else's commits.
63 *
64 * @param paths - Repository-relative paths to stage
65 * @param message - Commit message
66 * @param cwd - Repository root (default: the current directory)
67 * @returns Whether a commit was made and pushed
68 * @example
69 * ```ts
70 * const pushed = await commitAndPush(['bun.lock'], 'fix(deps): regenerate the lock file')
71 * ```
72 */
73export async function commitAndPush(paths: string[], message: string, cwd?: string): Promise<boolean> {
74 if (paths.length === 0)
75 return false
76
77 await runGit(['add', '--', ...paths], cwd)
78
79 // Nothing staged means the regeneration produced an identical file, which is
80 // a success with nothing to record rather than a failure.
81 const staged = await runGit(['diff', '--cached', '--name-only'], cwd)
82 if (!staged.trim())
83 return false
84
85 await runGit(['commit', '-m', message], cwd)
86
87 // The branch is whatever the job checked out. Pushing HEAD to it by name
88 // avoids assuming an upstream is configured.
89 const branch = (await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], cwd)).trim()
90 await runGit(['push', 'origin', `HEAD:${branch}`], cwd)
91
92 return true
93}