]> Git Repo - pico-vscode.git/blob - src/utils/downloadGit.mts
Merge branch 'main' into main
[pico-vscode.git] / src / utils / downloadGit.mts
1 import { createWriteStream, existsSync, rmSync } from "fs";
2 import { mkdir } from "fs/promises";
3 import { homedir, tmpdir } from "os";
4 import { join } from "path";
5 import Logger, { LoggerSource } from "../logger.mjs";
6 import { get } from "https";
7 import { exec } from "child_process";
8 import { HOME_VAR } from "../settings.mjs";
9 import { unxzFile, unzipFile } from "./downloadHelpers.mjs";
10 import { EXT_USER_AGENT } from "./githubREST.mjs";
11 import { unknownErrorToString } from "./errorHelper.mjs";
12
13 const GIT_DOWNLOAD_URL_WIN_AMD64 =
14   "https://github.com/git-for-windows/git/releases/download" +
15   "/v2.46.0.windows.1/MinGit-2.46.0-64-bit.zip";
16
17 /**
18  * Downloads and installs a portable version of Git.
19  *
20  * Supports Windows x64 and macOS x64 + amd64.
21  * But currently only execution on Windows x64 is enabled.
22  *
23  * @returns The path to the installed Git executable or undefined if the installation failed
24  */
25 export async function downloadGit(
26   redirectURL?: string
27 ): Promise<string | undefined> {
28   if (
29     process.platform !== "win32" ||
30     (process.platform === "win32" && process.arch !== "x64")
31   ) {
32     Logger.debug(
33       LoggerSource.gitDownloader,
34       "Portable Git installation only supported on Windows x64."
35     );
36
37     return;
38   }
39
40   const targetDirectory = join(homedir(), ".pico-sdk", "git");
41   const settingsTargetDirectory = `${HOME_VAR}/.pico-sdk/git`;
42
43   // Check if the Embed Python is already installed
44   if (redirectURL === undefined && existsSync(targetDirectory)) {
45     Logger.info(LoggerSource.gitDownloader, `Git is already installed.`);
46
47     return process.platform === "win32"
48       ? `${settingsTargetDirectory}/cmd/git.exe`
49       : `${settingsTargetDirectory}/bin/git`;
50   }
51
52   // Ensure the target directory exists
53   await mkdir(targetDirectory, { recursive: true });
54
55   // select download url for platform()_arch()
56   const downloadUrl = redirectURL ?? GIT_DOWNLOAD_URL_WIN_AMD64;
57
58   const tmpBasePath = join(tmpdir(), "pico-sdk");
59   await mkdir(tmpBasePath, { recursive: true });
60   const archiveFilePath = join(tmpBasePath, `git.zip`);
61
62   return new Promise(resolve => {
63     const requestOptions = {
64       headers: {
65         // eslint-disable-next-line @typescript-eslint/naming-convention
66         "User-Agent": EXT_USER_AGENT,
67         // eslint-disable-next-line @typescript-eslint/naming-convention
68         Accept: "*/*",
69         // eslint-disable-next-line @typescript-eslint/naming-convention
70         "Accept-Encoding": "gzip, deflate, br",
71       },
72     };
73
74     get(downloadUrl, requestOptions, response => {
75       const code = response.statusCode ?? 0;
76
77       if (code >= 400) {
78         //return reject(new Error(response.statusMessage));
79         Logger.error(
80           LoggerSource.gitDownloader,
81           "Downloading git failed:",
82           response.statusMessage ?? "{No status message vailable}."
83         );
84
85         resolve(undefined);
86       }
87
88       // handle redirects
89       if (code > 300 && code < 400 && !!response.headers.location) {
90         return resolve(downloadGit(response.headers.location));
91       }
92
93       // save the file to disk
94       const fileWriter = createWriteStream(archiveFilePath).on("finish", () => {
95         // TODO: maybe remove unused code-path here
96         if (process.platform === "darwin") {
97           unxzFile(archiveFilePath, targetDirectory)
98             .then(success => {
99               rmSync(archiveFilePath, { recursive: true, force: true });
100               resolve(
101                 success ? `${settingsTargetDirectory}/bin/git` : undefined
102               );
103             })
104             .catch(() => {
105               resolve(undefined);
106             });
107         } else {
108           // unpack the archive
109           const success = unzipFile(archiveFilePath, targetDirectory);
110           // delete tmp file
111           rmSync(archiveFilePath, { recursive: true, force: true });
112
113           if (success) {
114             // remove include section from gitconfig included in MiniGit
115             // which hardcodes the a path in Programm Files to be used by this git executable
116             exec(
117               `${
118                 process.env.ComSpec === "powershell.exe" ? "&" : ""
119               }"${targetDirectory}/cmd/git.exe" config ` +
120                 `--file "${targetDirectory}/etc/gitconfig" ` +
121                 "--remove-section include",
122               error => {
123                 if (error) {
124                   Logger.error(
125                     LoggerSource.gitDownloader,
126                     "Executing git failed:",
127                     unknownErrorToString(error)
128                   );
129                   resolve(undefined);
130                 } else {
131                   resolve(`${settingsTargetDirectory}/cmd/git.exe`);
132                 }
133               }
134             );
135           } else {
136             resolve(undefined);
137           }
138         }
139       });
140
141       response.pipe(fileWriter);
142     }).on("error", err => {
143       Logger.error(
144         LoggerSource.gitDownloader,
145         "Downloading git failed:",
146         err.message
147       );
148
149       return false;
150     });
151   });
152 }
This page took 0.036779 seconds and 4 git commands to generate.