|
1 | 1 | import { promisify } from "util";
|
2 | 2 | import { exec } from "child_process";
|
3 |
| -import Logger from "../logger.mjs"; |
| 3 | +import Logger, { LoggerSource } from "../logger.mjs"; |
4 | 4 | import { unlink } from "fs/promises";
|
5 | 5 | import type Settings from "../settings.mjs";
|
6 | 6 | import { SettingsKey, HOME_VAR } from "../settings.mjs";
|
7 | 7 | import { homedir } from "os";
|
8 | 8 | import which from "which";
|
9 | 9 | import { window } from "vscode";
|
10 | 10 | import { compareGe } from "./semverUtil.mjs";
|
| 11 | +import { downloadGit } from "./downloadGit.mjs"; |
11 | 12 |
|
12 | 13 | export const execAsync = promisify(exec);
|
13 | 14 |
|
14 |
| -export const MIN_GIT_VERSION="2.28.0"; |
| 15 | +export const MIN_GIT_VERSION = "2.28.0"; |
| 16 | +export const DEFAULT_GIT_EXE = "git"; |
15 | 17 |
|
16 |
| -export async function checkGitVersion(gitExecutable: string): |
17 |
| - Promise<[boolean, string]> |
18 |
| -{ |
19 |
| - const versionCommand = |
20 |
| - `${ |
21 |
| - process.env.ComSpec === "powershell.exe" ? "&" : "" |
22 |
| - }"${gitExecutable}" version`; |
23 |
| - const ret = await execAsync(versionCommand) |
| 18 | +export async function checkGitVersion( |
| 19 | + gitExecutable: string |
| 20 | +): Promise<[boolean, string]> { |
| 21 | + const versionCommand = `${ |
| 22 | + process.env.ComSpec === "powershell.exe" ? "&" : "" |
| 23 | + }"${gitExecutable}" version`; |
| 24 | + console.debug(`Checking git version: ${versionCommand}`); |
| 25 | + const ret = await execAsync(versionCommand); |
| 26 | + console.debug( |
| 27 | + `Git version check result: ${ret.stderr} stdout: ${ret.stdout}` |
| 28 | + ); |
24 | 29 | const regex = /git version (\d+\.\d+(\.\d+)*)/;
|
25 | 30 | const match = regex.exec(ret.stdout);
|
26 | 31 | if (match && match[1]) {
|
27 | 32 | const gitVersion = match[1];
|
| 33 | + console.debug(`Found git version: ${gitVersion}`); |
28 | 34 |
|
29 |
| - return [compareGe(gitVersion, MIN_GIT_VERSION), gitVersion]; |
| 35 | + return [compareGe(gitVersion, MIN_GIT_VERSION), gitVersion]; |
30 | 36 | } else {
|
| 37 | + console.debug( |
| 38 | + `Error: Could not parse git version from output: ${ret.stdout}` |
| 39 | + ); |
| 40 | + |
31 | 41 | return [false, "unknown"];
|
32 | 42 | }
|
33 | 43 | }
|
34 | 44 |
|
35 | 45 | /**
|
36 |
| - * Get installed version of git, and install it if it isn't already |
| 46 | + * Checks if a git executable is available or try to install if possible. |
| 47 | + * |
| 48 | + * @returns True if git is available, false otherwise or if |
| 49 | + * options.returnPath is true the path to the git executable. |
37 | 50 | */
|
38 |
| -export async function getGit(settings: Settings): Promise<string | undefined> { |
39 |
| - let gitExecutable: string | undefined = |
| 51 | +export async function ensureGit( |
| 52 | + settings: Settings, |
| 53 | + options?: { returnPath?: boolean } |
| 54 | +): Promise<string | boolean | undefined> { |
| 55 | + const returnPath = options?.returnPath ?? false; |
| 56 | + |
| 57 | + const resolveGitPath = (): string => |
40 | 58 | settings
|
41 | 59 | .getString(SettingsKey.gitPath)
|
42 |
| - ?.replace(HOME_VAR, homedir().replaceAll("\\", "/")) || "git"; |
43 |
| - let gitPath = await which(gitExecutable, { nothrow: true }); |
| 60 | + ?.replace(HOME_VAR, homedir().replaceAll("\\", "/")) || DEFAULT_GIT_EXE; |
| 61 | + |
| 62 | + let gitExe = resolveGitPath(); |
| 63 | + let gitPath = await which(gitExe, { nothrow: true }); |
| 64 | + |
| 65 | + // If custom path is invalid, offer to reset to default |
| 66 | + if (gitExe !== DEFAULT_GIT_EXE && gitPath === null) { |
| 67 | + const selection = await window.showErrorMessage( |
| 68 | + "The path to the git executable is invalid. " + |
| 69 | + "Do you want to reset it and search in system PATH?", |
| 70 | + { modal: true, detail: gitExe }, |
| 71 | + "Yes", |
| 72 | + "No" |
| 73 | + ); |
| 74 | + |
| 75 | + if (selection === "Yes") { |
| 76 | + await settings.updateGlobal(SettingsKey.gitPath, undefined); |
| 77 | + settings.reload(); |
| 78 | + gitExe = DEFAULT_GIT_EXE; |
| 79 | + gitPath = await which(DEFAULT_GIT_EXE, { nothrow: true }); |
| 80 | + } else { |
| 81 | + return returnPath ? undefined : false; |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + let isGitInstalled = gitPath !== null; |
44 | 86 | let gitVersion: string | undefined;
|
| 87 | + |
45 | 88 | if (gitPath !== null) {
|
46 |
| - const versionRet = await checkGitVersion(gitPath); |
47 |
| - if (!versionRet[0]) { |
| 89 | + const [isValid, version] = await checkGitVersion(gitPath); |
| 90 | + isGitInstalled = isValid; |
| 91 | + gitVersion = version; |
| 92 | + |
| 93 | + if (!isValid) { |
48 | 94 | gitPath = null;
|
49 | 95 | }
|
50 |
| - gitVersion = versionRet[1]; |
51 | 96 | }
|
52 |
| - if (gitPath === null) { |
53 |
| - // if git is not in path then checkForInstallationRequirements |
54 |
| - // maye downloaded it, so reload |
55 |
| - settings.reload(); |
56 |
| - gitExecutable = settings |
57 |
| - .getString(SettingsKey.gitPath) |
58 |
| - ?.replace(HOME_VAR, homedir().replaceAll("\\", "/")); |
59 |
| - if (gitExecutable === null || gitExecutable === undefined) { |
60 |
| - if (gitVersion !== undefined) { |
61 |
| - Logger.log(`Error: Found Git version ${gitVersion} - ` + |
62 |
| - `requires ${MIN_GIT_VERSION}.`); |
63 |
| - |
64 |
| - await window.showErrorMessage( |
65 |
| - `Found Git version ${gitVersion}, but requires ${MIN_GIT_VERSION}. ` + |
| 97 | + |
| 98 | + // Try download if invalid or not found |
| 99 | + if (!isGitInstalled) { |
| 100 | + // Win32 x64 only |
| 101 | + const downloadedGitPath = await downloadGit(); |
| 102 | + |
| 103 | + if (downloadedGitPath) { |
| 104 | + await settings.updateGlobal(SettingsKey.gitPath, downloadedGitPath); |
| 105 | + isGitInstalled = true; |
| 106 | + gitPath = downloadedGitPath; |
| 107 | + } else if (gitVersion) { |
| 108 | + Logger.error( |
| 109 | + LoggerSource.gitUtil, |
| 110 | + `Installed Git is too old (${gitVersion})` + |
| 111 | + "and a new version could not be downloaded." |
| 112 | + ); |
| 113 | + void window.showErrorMessage( |
| 114 | + `Found Git version ${gitVersion}, but requires ${MIN_GIT_VERSION}. ` + |
66 | 115 | "Please install and add to PATH or " +
|
67 |
| - "set the path to the git executable in global settings." |
68 |
| - ); |
69 |
| - } else { |
70 |
| - Logger.log("Error: Git not found."); |
71 |
| - |
72 |
| - await window.showErrorMessage( |
73 |
| - "Git not found. Please install and add to PATH or " + |
74 |
| - "set the path to the git executable in global settings." |
75 |
| - ); |
76 |
| - } |
77 |
| - |
78 |
| - return undefined; |
| 116 | + "set the path to the git executable in global settings." |
| 117 | + ); |
79 | 118 | } else {
|
80 |
| - gitPath = await which(gitExecutable, { nothrow: true }); |
| 119 | + Logger.error( |
| 120 | + LoggerSource.gitUtil, |
| 121 | + "Git is not installed and could not be downloaded." |
| 122 | + ); |
| 123 | + void window.showErrorMessage( |
| 124 | + "Git is not installed. Please install and add to PATH or " + |
| 125 | + "set the path to the git executable in global settings." + |
| 126 | + (process.platform === "darwin" |
| 127 | + ? " You can install it by running " + |
| 128 | + "`xcode-select --install` in the terminal." |
| 129 | + : "") |
| 130 | + ); |
81 | 131 | }
|
82 | 132 | }
|
83 | 133 |
|
84 |
| - return gitPath || undefined; |
| 134 | + return returnPath ? gitPath || undefined : isGitInstalled; |
85 | 135 | }
|
86 | 136 |
|
87 | 137 | /**
|
@@ -168,7 +218,7 @@ export async function sparseCloneRepository(
|
168 | 218 | await execAsync(cloneCommand);
|
169 | 219 | await execAsync(
|
170 | 220 | `cd ${
|
171 |
| - process.env.ComSpec?.endsWith("cmd.exe") ? "/d " : " " |
| 221 | + process.env.ComSpec?.endsWith("cmd.exe") ? "/d " : " " |
172 | 222 | }"${targetDirectory}" && ${
|
173 | 223 | process.env.ComSpec === "powershell.exe" ? "&" : ""
|
174 | 224 | }"${gitExecutable}" sparse-checkout set --cone`
|
@@ -212,7 +262,7 @@ export async function sparseCheckout(
|
212 | 262 | try {
|
213 | 263 | await execAsync(
|
214 | 264 | `cd ${
|
215 |
| - process.env.ComSpec?.endsWith("cmd.exe") ? "/d " : " " |
| 265 | + process.env.ComSpec?.endsWith("cmd.exe") ? "/d " : " " |
216 | 266 | } "${repoDirectory}" && ${
|
217 | 267 | process.env.ComSpec === "powershell.exe" ? "&" : ""
|
218 | 268 | }"${gitExecutable}" sparse-checkout add ${checkoutPath}`
|
|
0 commit comments