diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e7b2b0c8..54198850 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,5 +9,10 @@ "reveal": "never", }, }, + { + "type": "npm", + "script": "build", + "label": "build", + }, ], } diff --git a/src/extension.ts b/src/extension.ts index b6971c10..9c0361a4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -31,10 +31,11 @@ async function openCommand(): Promise { if (!editor) { throw new Error('No active editor') } - const [remoteURL, branch, fileRelative] = await repoInfo(editor.document.uri.fsPath) - if (remoteURL === '') { + const repositoryInfo = await repoInfo(editor.document.uri.fsPath) + if (!repositoryInfo) { return } + const { remoteURL, branch, fileRelative } = repositoryInfo // Open in browser. await open( @@ -59,7 +60,11 @@ async function searchCommand(): Promise { if (!editor) { throw new Error('No active editor') } - const [remoteURL, branch, fileRelative] = await repoInfo(editor.document.uri.fsPath) + const repositoryInfo = await repoInfo(editor.document.uri.fsPath) + if (!repositoryInfo) { + return + } + const { remoteURL, branch, fileRelative } = repositoryInfo const query = editor.document.getText(editor.selection) if (query === '') { diff --git a/src/git.ts b/src/git.ts index ea892745..edc5a0c4 100644 --- a/src/git.ts +++ b/src/git.ts @@ -4,26 +4,12 @@ import { log } from './log' import { getRemoteUrlReplacements } from './config' /** - * Returns [remote, upstream branch]. - * Empty remote is returned if the upstream branch points to a local branch. - * Empty upstream branch is returned if there is no upstream branch. + * Returns the repository root directory for any directory within the + * repository. */ -async function gitRemoteBranch(repoDirectory: string): Promise<[string, string]> { - try { - const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD@{upstream}'], { cwd: repoDirectory }) - const remoteAndBranch = stdout.split('/') - if (remoteAndBranch.length === 2) { - const [remote, branch] = remoteAndBranch - return [remote, branch] - } - if (remoteAndBranch.length === 1) { - // The upstream branch points to a local branch. - return ['', remoteAndBranch[0]] - } - return ['', ''] - } catch { - return ['', ''] - } +async function gitRootDirectory(repoDirectory: string): Promise { + const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], { cwd: repoDirectory }) + return stdout } /** @@ -51,74 +37,92 @@ async function gitRemoteURL(repoDirectory: string, remoteName: string): Promise< return stdout } +interface RemoteName { + /** + * Remote name of the upstream repository, + * or the first found remote name if no upstream is found + */ + remoteName: string +} + +interface Branch { + /** + * Remote branch name, or 'HEAD' if it isn't found because + * e.g. detached HEAD state, upstream branch points to a local branch + */ + branch: string +} + /** - * Returns the remote URL. + * Returns the remote name and branch + * + * @param repoDirectory the repository root directory */ -async function gitDefaultRemoteURL(repoDirectory: string): Promise { - const [remote] = await gitRemoteBranch(repoDirectory) - if (remote !== '') { - return gitRemoteURL(repoDirectory, remote) +async function gitRemoteNameAndBranch(repoDirectory: string): Promise { + let remoteName: string | undefined + let branch = 'HEAD' + + const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD@{upstream}'], { cwd: repoDirectory }) + const remoteAndBranch = stdout.split('/') + + if (remoteAndBranch.length === 1) { + // The upstream branch points to a local branch. + ;[remoteName] = remoteAndBranch + } + if (remoteAndBranch.length === 2) { + ;[remoteName, branch] = remoteAndBranch } // If we cannot find the remote name deterministically, we use the first // Git remote found. - const remotes = await gitRemotes(repoDirectory) - if (remotes.length === 0) { - throw new Error('no configured git remotes') + if (!remoteName) { + const remotes = await gitRemotes(repoDirectory) + if (remotes.length > 1) { + log.appendLine(`using first git remote: ${remotes[0]}`) + remoteName = remotes[0] + } } - if (remotes.length > 1) { - log.appendLine(`using first git remote: ${remotes[0]}`) + + // Throw if a remote still isn't found + if (!remoteName) { + throw new Error('no configured git remotes') } - return gitRemoteURL(repoDirectory, remotes[0]) -} -/** - * Returns the repository root directory for any directory within the - * repository. - */ -async function gitRootDirectory(repoDirectory: string): Promise { - const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], { cwd: repoDirectory }) - return stdout + return { remoteName, branch } } -/** - * Returns either the current remote branch name of the repository OR in all - * other cases (e.g. detached HEAD state, upstream branch points to a local - * branch), it returns "HEAD". - */ -async function gitBranch(repoDirectory: string): Promise { - const [origin, branch] = await gitRemoteBranch(repoDirectory) - if (origin !== '') { - // The remote branch exists. - return branch - } - return 'HEAD' +interface RepositoryInfo extends Branch { + /** Git repository remote URL */ + remoteURL: string + + /** File path relative to the repository root */ + fileRelative: string } /** * Returns the Git repository remote URL, the current branch, and the file path - * relative to the repository root. Empty strings are returned if this cannot be - * determined. + * relative to the repository root. Returns undefined if no remote is found */ -export async function repoInfo(filePath: string): Promise<[string, string, string]> { - let remoteURL = '' - let branch = '' - let fileRelative = '' +export async function repoInfo(filePath: string): Promise { try { // Determine repository root directory. const fileDirectory = path.dirname(filePath) const repoRoot = await gitRootDirectory(fileDirectory) - // Determine file path, relative to repository root. - fileRelative = filePath.slice(repoRoot.length + 1) - remoteURL = await gitDefaultRemoteURL(repoRoot) - branch = await gitBranch(repoRoot) + // Determine file path relative to repository root. + let fileRelative = filePath.slice(repoRoot.length + 1) + + const remoteNameAndBranch = await gitRemoteNameAndBranch(repoRoot) + const { branch, remoteName } = remoteNameAndBranch + const remoteURL = await gitRemoteURL(repoRoot, remoteName) + if (process.platform === 'win32') { fileRelative = fileRelative.replace(/\\/g, '/') } + log.appendLine(`repoInfo(${filePath}): remoteURL="${remoteURL}" branch="${branch}" fileRel="${fileRelative}"`) + return { remoteURL, branch, fileRelative } } catch (error) { log.appendLine(`repoInfo(${filePath}): ${error as string}`) + return undefined } - log.appendLine(`repoInfo(${filePath}): remoteURL="${remoteURL}" branch="${branch}" fileRel="${fileRelative}"`) - return [remoteURL, branch, fileRelative] }