Repository navigation
Expand file tree
/
Copy pathgit_commit_adapter.ts
More file actions
109 lines (99 loc) · 6.27 KB
/
Copy pathgit_commit_adapter.ts
File metadata and controls
109 lines (99 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { GitPushFailure } from '../domain/git_push_failure';
import { captureGitPushDiagnostic } from './git_push_diagnostic';
import { verificationExecutable } from './verification_executable';
import { nativeVerificationRetainsProfile } from './native_verification_profiles';
import * as exec from '@actions/exec';
import type { GitCommitPort } from '../application/ports/git_ports';
import { buildGitAuthenticationEnvironment } from './git_authentication_environment';
import { prepareUntrustedCommandEnvironment } from './untrusted_command_environment';
import { withoutGitHooks } from './git_hook_isolation';
import { assertVerificationRuntimePrerequisites } from './verification_runtime_preflight';
import { DISABLED_VERIFICATION_SETUP, type PreparedVerificationSetup } from '../application/ports/verification_setup_ports';
import { prepareRepositoryVerification } from './verification_setup_runner';
import { captureSourceDependencyPreparation, type SourceDependencyPreparation } from './source_verification_dependencies';
import { captureVerificationPythonLibraries } from './verification_python_library_environment';
type GitCommandOptions = { stdout?: (data: Buffer) => void; stderr?: (data: Buffer) => void; env?: Record<string, string>; untrusted?: boolean; verifyRuntimePrerequisites?: boolean; resolveFromEnvironment?: boolean };
type GitCommandExecutor = (program: string, args: string[], options?: GitCommandOptions) => Promise<number>;
export class GitCommitAdapter implements GitCommitPort {
private readonly preparePythonLibraries: (environment: Record<string, string>) => void;
constructor(
private readonly executeCommand: GitCommandExecutor = (program, args, options) => options
? exec.exec(options.resolveFromEnvironment && options.env ? verificationExecutable(program, options.env) : program, args, {
// Captured Git reads can contain NUL-delimited records that
// hide subsequent lines in GitHub's Actions log viewer.
...(options.stdout || options.stderr ? { silent: true } : {}),
...(options.stdout || options.stderr ? { listeners: { ...(options.stdout ? { stdout: options.stdout } : {}),
...(options.stderr ? { stderr: options.stderr } : {}) } } : {}),
...(options.env ? { env: options.env } : {}),
})
: exec.exec(program, args),
private readonly checkPrerequisites: (environment: Record<string, string>) => void = assertVerificationRuntimePrerequisites,
private readonly verificationSetup: PreparedVerificationSetup = DISABLED_VERIFICATION_SETUP,
private readonly prepareSourceDependencies: SourceDependencyPreparation = captureSourceDependencyPreparation(),
) {
this.preparePythonLibraries = captureVerificationPythonLibraries(
verificationSetup.kind === 'ready' ? verificationSetup.toolRoot : process.env.RUNNER_TOOL_CACHE,
);
}
async execute(program: string, args: string[], options?: GitCommandOptions): Promise<number> {
if (options?.verifyRuntimePrerequisites && !options.untrusted) throw new Error('Runtime preflight requires credential-free verification.');
if (!options?.untrusted) return options ? this.executeCommand(program, args, options) : this.executeCommand(program, args);
if (options.env) throw new Error('Untrusted command execution does not accept a caller-supplied environment.');
const runtime = prepareUntrustedCommandEnvironment();
try {
if (options.verifyRuntimePrerequisites) {
await prepareRepositoryVerification(this.verificationSetup, runtime.environment);
this.preparePythonLibraries(runtime.environment);
this.checkPrerequisites(runtime.environment);
await this.prepareSourceDependencies(runtime.environment);
}
return await this.executeCommand(program, args, {
...(options.stdout ? { stdout: options.stdout } : {}),
env: runtime.environment,
...(options.verifyRuntimePrerequisites && this.verificationSetup.kind === 'ready' ? { resolveFromEnvironment: true } : {}),
});
} finally {
if (!nativeVerificationRetainsProfile(runtime.environment.HOME)) runtime.cleanup();
}
}
async configureAuthor(name: string, email: string): Promise<void> {
await this.execute('git', ['config', 'user.name', name]);
await this.execute('git', ['config', 'user.email', email]);
}
async fetch(branch: string, token?: string): Promise<void> {
if (await this.executeAuthenticated(['fetch', 'origin', branch], token) !== 0) {
throw new Error('Git fetch did not complete successfully.');
}
}
async stageAll(): Promise<void> {
await this.execute('git', ['add', '-A']);
}
async stagePaths(paths: string[]): Promise<void> {
if (paths.length > 0) await this.execute('git', ['add', '--', ...paths]);
}
async commit(message: string): Promise<void> {
await withoutGitHooks(emptyHooks => this.execute('git', ['-c', `core.hooksPath=${emptyHooks}`, 'commit', '-m', message], { untrusted: true }));
}
async push(branch: string, token?: string): Promise<void> {
const diagnostic = captureGitPushDiagnostic();
try {
const code = await this.executeAuthenticated(['push', 'origin', branch], token, diagnostic.stderr);
if (code !== 0) throw new GitPushFailure(diagnostic.classify());
} catch {
throw new GitPushFailure(diagnostic.classify());
}
}
private async executeAuthenticated(args: string[], token?: string, stderr?: (data: Buffer) => void): Promise<number> {
if (!token?.trim()) {
return stderr ? this.execute('git', args, { stderr }) : this.execute('git', args);
}
const environment = buildGitAuthenticationEnvironment(token);
return this.execute('git', args, {
...(stderr ? { stderr } : {}),
// Supply authentication only to this trusted git subprocess. The
// agent process never receives this value and nothing is persisted
// in the repository's git configuration or remote URL.
...(environment ? { env: environment } : {}),
});
}
}