Skip to content
This repository was archived by the owner on Mar 10, 2022. It is now read-only.

Commit cbbfac2

Browse files
authored
refactor: simplify git helpers (#134)
1 parent 06580d4 commit cbbfac2

File tree

3 files changed

+79
-65
lines changed

3 files changed

+79
-65
lines changed

.vscode/tasks.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@
99
"reveal": "never",
1010
},
1111
},
12+
{
13+
"type": "npm",
14+
"script": "build",
15+
"label": "build",
16+
},
1217
],
1318
}

src/extension.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ async function openCommand(): Promise<void> {
3131
if (!editor) {
3232
throw new Error('No active editor')
3333
}
34-
const [remoteURL, branch, fileRelative] = await repoInfo(editor.document.uri.fsPath)
35-
if (remoteURL === '') {
34+
const repositoryInfo = await repoInfo(editor.document.uri.fsPath)
35+
if (!repositoryInfo) {
3636
return
3737
}
38+
const { remoteURL, branch, fileRelative } = repositoryInfo
3839

3940
// Open in browser.
4041
await open(
@@ -59,7 +60,11 @@ async function searchCommand(): Promise<void> {
5960
if (!editor) {
6061
throw new Error('No active editor')
6162
}
62-
const [remoteURL, branch, fileRelative] = await repoInfo(editor.document.uri.fsPath)
63+
const repositoryInfo = await repoInfo(editor.document.uri.fsPath)
64+
if (!repositoryInfo) {
65+
return
66+
}
67+
const { remoteURL, branch, fileRelative } = repositoryInfo
6368

6469
const query = editor.document.getText(editor.selection)
6570
if (query === '') {

src/git.ts

Lines changed: 66 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,12 @@ import { log } from './log'
44
import { getRemoteUrlReplacements } from './config'
55

66
/**
7-
* Returns [remote, upstream branch].
8-
* Empty remote is returned if the upstream branch points to a local branch.
9-
* Empty upstream branch is returned if there is no upstream branch.
7+
* Returns the repository root directory for any directory within the
8+
* repository.
109
*/
11-
async function gitRemoteBranch(repoDirectory: string): Promise<[string, string]> {
12-
try {
13-
const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD@{upstream}'], { cwd: repoDirectory })
14-
const remoteAndBranch = stdout.split('/')
15-
if (remoteAndBranch.length === 2) {
16-
const [remote, branch] = remoteAndBranch
17-
return [remote, branch]
18-
}
19-
if (remoteAndBranch.length === 1) {
20-
// The upstream branch points to a local branch.
21-
return ['', remoteAndBranch[0]]
22-
}
23-
return ['', '']
24-
} catch {
25-
return ['', '']
26-
}
10+
async function gitRootDirectory(repoDirectory: string): Promise<string> {
11+
const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], { cwd: repoDirectory })
12+
return stdout
2713
}
2814

2915
/**
@@ -51,74 +37,92 @@ async function gitRemoteURL(repoDirectory: string, remoteName: string): Promise<
5137
return stdout
5238
}
5339

40+
interface RemoteName {
41+
/**
42+
* Remote name of the upstream repository,
43+
* or the first found remote name if no upstream is found
44+
*/
45+
remoteName: string
46+
}
47+
48+
interface Branch {
49+
/**
50+
* Remote branch name, or 'HEAD' if it isn't found because
51+
* e.g. detached HEAD state, upstream branch points to a local branch
52+
*/
53+
branch: string
54+
}
55+
5456
/**
55-
* Returns the remote URL.
57+
* Returns the remote name and branch
58+
*
59+
* @param repoDirectory the repository root directory
5660
*/
57-
async function gitDefaultRemoteURL(repoDirectory: string): Promise<string> {
58-
const [remote] = await gitRemoteBranch(repoDirectory)
59-
if (remote !== '') {
60-
return gitRemoteURL(repoDirectory, remote)
61+
async function gitRemoteNameAndBranch(repoDirectory: string): Promise<RemoteName & Branch> {
62+
let remoteName: string | undefined
63+
let branch = 'HEAD'
64+
65+
const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD@{upstream}'], { cwd: repoDirectory })
66+
const remoteAndBranch = stdout.split('/')
67+
68+
if (remoteAndBranch.length === 1) {
69+
// The upstream branch points to a local branch.
70+
;[remoteName] = remoteAndBranch
71+
}
72+
if (remoteAndBranch.length === 2) {
73+
;[remoteName, branch] = remoteAndBranch
6174
}
6275

6376
// If we cannot find the remote name deterministically, we use the first
6477
// Git remote found.
65-
const remotes = await gitRemotes(repoDirectory)
66-
if (remotes.length === 0) {
67-
throw new Error('no configured git remotes')
78+
if (!remoteName) {
79+
const remotes = await gitRemotes(repoDirectory)
80+
if (remotes.length > 1) {
81+
log.appendLine(`using first git remote: ${remotes[0]}`)
82+
remoteName = remotes[0]
83+
}
6884
}
69-
if (remotes.length > 1) {
70-
log.appendLine(`using first git remote: ${remotes[0]}`)
85+
86+
// Throw if a remote still isn't found
87+
if (!remoteName) {
88+
throw new Error('no configured git remotes')
7189
}
72-
return gitRemoteURL(repoDirectory, remotes[0])
73-
}
7490

75-
/**
76-
* Returns the repository root directory for any directory within the
77-
* repository.
78-
*/
79-
async function gitRootDirectory(repoDirectory: string): Promise<string> {
80-
const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], { cwd: repoDirectory })
81-
return stdout
91+
return { remoteName, branch }
8292
}
8393

84-
/**
85-
* Returns either the current remote branch name of the repository OR in all
86-
* other cases (e.g. detached HEAD state, upstream branch points to a local
87-
* branch), it returns "HEAD".
88-
*/
89-
async function gitBranch(repoDirectory: string): Promise<string> {
90-
const [origin, branch] = await gitRemoteBranch(repoDirectory)
91-
if (origin !== '') {
92-
// The remote branch exists.
93-
return branch
94-
}
95-
return 'HEAD'
94+
interface RepositoryInfo extends Branch {
95+
/** Git repository remote URL */
96+
remoteURL: string
97+
98+
/** File path relative to the repository root */
99+
fileRelative: string
96100
}
97101

98102
/**
99103
* Returns the Git repository remote URL, the current branch, and the file path
100-
* relative to the repository root. Empty strings are returned if this cannot be
101-
* determined.
104+
* relative to the repository root. Returns undefined if no remote is found
102105
*/
103-
export async function repoInfo(filePath: string): Promise<[string, string, string]> {
104-
let remoteURL = ''
105-
let branch = ''
106-
let fileRelative = ''
106+
export async function repoInfo(filePath: string): Promise<RepositoryInfo | undefined> {
107107
try {
108108
// Determine repository root directory.
109109
const fileDirectory = path.dirname(filePath)
110110
const repoRoot = await gitRootDirectory(fileDirectory)
111111

112-
// Determine file path, relative to repository root.
113-
fileRelative = filePath.slice(repoRoot.length + 1)
114-
remoteURL = await gitDefaultRemoteURL(repoRoot)
115-
branch = await gitBranch(repoRoot)
112+
// Determine file path relative to repository root.
113+
let fileRelative = filePath.slice(repoRoot.length + 1)
114+
115+
const remoteNameAndBranch = await gitRemoteNameAndBranch(repoRoot)
116+
const { branch, remoteName } = remoteNameAndBranch
117+
const remoteURL = await gitRemoteURL(repoRoot, remoteName)
118+
116119
if (process.platform === 'win32') {
117120
fileRelative = fileRelative.replace(/\\/g, '/')
118121
}
122+
log.appendLine(`repoInfo(${filePath}): remoteURL="${remoteURL}" branch="${branch}" fileRel="${fileRelative}"`)
123+
return { remoteURL, branch, fileRelative }
119124
} catch (error) {
120125
log.appendLine(`repoInfo(${filePath}): ${error as string}`)
126+
return undefined
121127
}
122-
log.appendLine(`repoInfo(${filePath}): remoteURL="${remoteURL}" branch="${branch}" fileRel="${fileRelative}"`)
123-
return [remoteURL, branch, fileRelative]
124128
}

0 commit comments

Comments
 (0)