* Spawns a given Git command process. Does not throw if the command fails. Additionally, * if there is any stderr output, the output will be printed. This makes it easier to * info failed commands.
(args: string[], options: GitCommandRunOptions = {})
| 82 | * info failed commands. |
| 83 | */ |
| 84 | runGraceful(args: string[], options: GitCommandRunOptions = {}): SpawnSyncReturns<string> { |
| 85 | /** The git command to be run. */ |
| 86 | const gitCommand = args[0]; |
| 87 | |
| 88 | if (isDryRun() && gitCommand === 'push') { |
| 89 | Log.debug(`"git push" is not able to be run in dryRun mode.`); |
| 90 | throw new DryRunError(); |
| 91 | } |
| 92 | |
| 93 | // Clear the credential helper that is used, preventing the temporary token from being saved as a |
| 94 | // valid token for future use. |
| 95 | args = ['-c', 'credential.helper=', ...args]; |
| 96 | // To improve the debugging experience in case something fails, we print all executed Git |
| 97 | // commands at the DEBUG level to better understand the git actions occurring. |
| 98 | // Note that we sanitize the command before printing it to the console. We do not want to |
| 99 | // print an access token if it is contained in the command. It's common to share errors with |
| 100 | // others if the tool failed, and we do not want to leak tokens. |
| 101 | Log.debug('Executing: git', this.sanitizeConsoleOutput(args.join(' '))); |
| 102 | |
| 103 | const result = spawnSync(this.gitBinPath, args, { |
| 104 | cwd: this.baseDir, |
| 105 | stdio: 'pipe', |
| 106 | ...options, |
| 107 | // Encoding is always `utf8` and not overridable. This ensures that this method |
| 108 | // always returns `string` as output instead of buffers. |
| 109 | encoding: 'utf8', |
| 110 | }); |
| 111 | |
| 112 | Log.debug(`Status: ${result.status}, Error: ${!!result.error}, Signal: ${result.signal}`); |
| 113 | |
| 114 | if (result.status !== 0 && result.stderr !== null && result.stderr !== undefined) { |
| 115 | // Git sometimes prints the command if it failed. This means that it could |
| 116 | // potentially leak the Github token used for accessing the remote. To avoid |
| 117 | // printing a token, we sanitize the string before printing the stderr output. |
| 118 | process.stderr.write(this.sanitizeConsoleOutput(result.stderr)); |
| 119 | } |
| 120 | |
| 121 | Log.debug('Stdout:', result.stdout ? this.sanitizeConsoleOutput(result.stdout) : result.stdout); |
| 122 | Log.debug('Stderr:', result.stderr ? this.sanitizeConsoleOutput(result.stderr) : result.stderr); |
| 123 | |
| 124 | if (result.error !== undefined) { |
| 125 | Log.debug('Process Error:', this.sanitizeConsoleOutput(result.error.message)); |
| 126 | if (result.error.stack) { |
| 127 | Log.debug('Process Error Stack:', this.sanitizeConsoleOutput(result.error.stack)); |
| 128 | } |
| 129 | // Git sometimes prints the command if it failed. This means that it could |
| 130 | // potentially leak the Github token used for accessing the remote. To avoid |
| 131 | // printing a token, we sanitize the string before printing the stderr output. |
| 132 | process.stderr.write(this.sanitizeConsoleOutput(result.error.message)); |
| 133 | } |
| 134 | |
| 135 | return result; |
| 136 | } |
| 137 | |
| 138 | /** Git URL that resolves to the configured repository. */ |
| 139 | getRepoGitUrl() { |
no test coverage detected