|
1 | | -import * as path from "path" |
2 | | -import * as fs from "mz/fs" |
3 | | -import * as ini from "ini" |
| 1 | +import * as vscode from "vscode" |
| 2 | +import { outputChannel } from "./extension" |
| 3 | +import { GitExtension, Remote, Repository } from "./vscode-git" |
4 | 4 |
|
5 | | -interface IRemote { |
6 | | - fetch: string |
7 | | - url?: string |
| 5 | +async function getGitAPI() { |
| 6 | + const gitExtension = |
| 7 | + vscode.extensions.getExtension<GitExtension>("vscode.git") |
| 8 | + if (!gitExtension) return null |
| 9 | + const exports = await gitExtension.activate() |
| 10 | + return exports.getAPI(1) |
8 | 11 | } |
9 | 12 |
|
10 | | -interface IGitDirectories { |
11 | | - git: string |
12 | | - repository: string |
| 13 | +export async function getRepo(fileUri: vscode.Uri) { |
| 14 | + const api = await getGitAPI() |
| 15 | + if (!api) { |
| 16 | + vscode.window.showErrorMessage("Git API not available") |
| 17 | + return null |
| 18 | + } |
| 19 | + const existing = api.getRepository(fileUri) |
| 20 | + if (existing) return existing |
| 21 | + outputChannel.appendLine("No repository found, triggering git refresh...") |
| 22 | + await vscode.commands.executeCommand("git.refresh") |
| 23 | + const afterRefresh = api.getRepository(fileUri) |
| 24 | + if (afterRefresh) return afterRefresh |
| 25 | + // Repos are discovered asynchronously after activation; wait for one to open |
| 26 | + return new Promise<ReturnType<typeof api.getRepository>>((resolve) => { |
| 27 | + const timeout = setTimeout(() => { |
| 28 | + disposable.dispose() |
| 29 | + resolve(null) |
| 30 | + outputChannel.appendLine("Hit 5 second timeout for repository to load.") |
| 31 | + }, 5000) |
| 32 | + const disposable = api.onDidOpenRepository(() => { |
| 33 | + const repo = api.getRepository(fileUri) |
| 34 | + if (repo) { |
| 35 | + outputChannel.appendLine("Found repository via onDidOpenRepository.") |
| 36 | + clearTimeout(timeout) |
| 37 | + disposable.dispose() |
| 38 | + resolve(repo) |
| 39 | + } |
| 40 | + }) |
| 41 | + }) |
13 | 42 | } |
14 | 43 |
|
15 | 44 | export async function origin( |
16 | | - gitDir: string, |
| 45 | + repository: Repository, |
17 | 46 | remote: string, |
18 | 47 | ): Promise<string | null> { |
19 | | - const configPath = path.resolve(gitDir, "config") |
20 | | - if (!(await fs.exists(configPath))) { |
21 | | - return null |
22 | | - } |
23 | | - const configFileData = await fs.readFile(configPath, { encoding: "utf-8" }) |
24 | | - const parsedConfig = ini.parse(configFileData) |
25 | | - for (const [key, value] of Object.entries(parsedConfig)) { |
26 | | - if (key.startsWith('remote "')) { |
27 | | - const origin = key.replace(/^remote "/, "").replace(/"$/, "") |
28 | | - if (origin === remote) { |
29 | | - const url = (value as IRemote).url |
30 | | - return url || null |
31 | | - } |
32 | | - } |
33 | | - } |
34 | | - |
35 | | - return null |
| 48 | + const found = repository.state.remotes.find((r: Remote) => r.name === remote) |
| 49 | + return found?.fetchUrl ?? found?.pushUrl ?? null |
36 | 50 | } |
37 | 51 |
|
38 | | -/** Get the SHA for a ref */ |
39 | 52 | export async function getSHAForBranch( |
40 | | - gitDir: string, |
| 53 | + repository: Repository, |
41 | 54 | branchName: string, |
42 | 55 | ): Promise<string | null> { |
43 | | - const refName = `refs/heads/${branchName}` |
44 | | - // check for normal ref |
45 | | - const refPath = path.resolve(gitDir, refName) |
46 | | - if (await fs.exists(refPath)) { |
47 | | - return await fs.readFile(refPath, { |
48 | | - encoding: "utf-8", |
49 | | - }) |
50 | | - } |
51 | | - // check packed-refs |
52 | | - const packedRefPath = path.resolve(gitDir, "packed-refs") |
53 | | - if (await fs.exists(packedRefPath)) { |
54 | | - const packRefs = await fs.readFile(packedRefPath, { |
55 | | - encoding: "utf-8", |
56 | | - }) |
57 | | - |
58 | | - for (const x of packRefs.split("\n")) { |
59 | | - const [sha, refPath] = x.split(" ") as [ |
60 | | - string | undefined, |
61 | | - string | undefined, |
62 | | - ] |
63 | | - if (sha && refPath && refPath.trim() === refName.trim()) { |
64 | | - return sha |
65 | | - } |
66 | | - } |
| 56 | + try { |
| 57 | + const branch = await repository.getBranch(branchName) |
| 58 | + return branch.commit ?? null |
| 59 | + } catch { |
| 60 | + return null |
67 | 61 | } |
68 | | - return null |
69 | 62 | } |
70 | 63 |
|
71 | | -/** Get the current SHA and branch from HEAD for a git directory */ |
72 | 64 | export async function head( |
73 | | - gitDir: string, |
| 65 | + repository: Repository, |
74 | 66 | ): Promise<[string, string | null] | null> { |
75 | | - const headPath = path.resolve(gitDir, "HEAD") |
76 | | - if (!(await fs.exists(headPath))) { |
77 | | - return null |
78 | | - } |
79 | | - const headFileData = await fs.readFile(headPath, { encoding: "utf-8" }) |
80 | | - if (!headFileData) { |
81 | | - return null |
82 | | - } |
83 | | - // If we're not on a branch, headFileData will be of the form: |
84 | | - // `3c0cc80bbdb682f6e9f65b4c9659ca21924aad4` |
85 | | - // If we're on a branch, it will be `ref: refs/heads/my_branch_name` |
86 | | - const [maybeSha, maybeHeadInfo] = headFileData.split(" ") as [ |
87 | | - string, |
88 | | - string | undefined, |
89 | | - ] |
90 | | - if (maybeHeadInfo == null) { |
91 | | - return [maybeSha.trim(), null] |
92 | | - } |
93 | | - const branchName = maybeHeadInfo.trim().replace("refs/heads/", "") |
94 | | - const sha = await getSHAForBranch(gitDir, branchName) |
95 | | - if (sha == null) { |
96 | | - return null |
97 | | - } |
98 | | - return [sha.trim(), branchName] |
99 | | -} |
100 | | - |
101 | | -export function dir(filePath: string) { |
102 | | - return walkUpDirectories(filePath, ".git") |
| 67 | + const repoHead = repository.state.HEAD |
| 68 | + if (!repoHead?.commit) return null |
| 69 | + return [repoHead.commit, repoHead.name ?? null] |
103 | 70 | } |
104 | 71 |
|
105 | | -function walkUpDirectories( |
106 | | - file_path: string, |
107 | | - file_or_folder: string, |
108 | | -): IGitDirectories | null { |
109 | | - let directory = file_path |
110 | | - while (true) { |
111 | | - const newPath = path.resolve(directory, file_or_folder) |
112 | | - if (fs.existsSync(newPath)) { |
113 | | - if (fs.lstatSync(newPath).isFile()) { |
114 | | - const submoduleMatch = fs |
115 | | - .readFileSync(newPath, "utf8") |
116 | | - .match(/gitdir: (.+)/) |
117 | | - |
118 | | - if (submoduleMatch) { |
119 | | - return { |
120 | | - git: path.resolve(path.join(directory, submoduleMatch[1])), |
121 | | - repository: directory, |
122 | | - } |
123 | | - } else { |
124 | | - return null |
125 | | - } |
126 | | - } else { |
127 | | - return { |
128 | | - git: newPath, |
129 | | - repository: directory, |
130 | | - } |
131 | | - } |
132 | | - } |
133 | | - const newDirectory = path.dirname(directory) |
134 | | - if (newDirectory === directory) { |
135 | | - return null |
136 | | - } |
137 | | - directory = newDirectory |
138 | | - } |
| 72 | +export async function dir( |
| 73 | + repository: Repository, |
| 74 | +): Promise<{ git: string; repository: string } | null> { |
| 75 | + const root = repository.rootUri.fsPath |
| 76 | + return { git: root, repository: root } |
139 | 77 | } |
0 commit comments