diff options
Diffstat (limited to 'source/github-helpers')
-rw-r--r-- | source/github-helpers/get-current-git-ref.test.ts | 114 | ||||
-rw-r--r-- | source/github-helpers/get-current-git-ref.ts | 32 | ||||
-rw-r--r-- | source/github-helpers/get-default-branch.ts | 42 | ||||
-rw-r--r-- | source/github-helpers/github-url.ts | 4 | ||||
-rw-r--r-- | source/github-helpers/index.ts | 50 | ||||
-rw-r--r-- | source/github-helpers/is-default-branch.ts | 13 |
6 files changed, 177 insertions, 78 deletions
diff --git a/source/github-helpers/get-current-git-ref.test.ts b/source/github-helpers/get-current-git-ref.test.ts new file mode 100644 index 00000000..9581f5db --- /dev/null +++ b/source/github-helpers/get-current-git-ref.test.ts @@ -0,0 +1,114 @@ +import {assert, test} from 'vitest'; + +// @ts-expect-error JS only +import {navigateToBranch} from '../../test/fixtures/globals.js'; +import getCurrentGitRef from './get-current-git-ref.js'; + +// The titles supplied here listed here are real, not guessed, except the error tester +test('getCurrentGitRef', () => { + // Error testing + assert.equal(getCurrentGitRef( + '/', + 'some page title', + ), undefined, 'It should never throw with valid input'); + assert.throws(() => getCurrentGitRef( + 'https://github.com', + 'github.com', + )); + + // Root + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint', + 'typescript-eslint/typescript-eslint: Monorepo for all the tooling which enables ESLint to support TypeScript', + ), undefined); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/tree/chore/lerna-4', + 'typescript-eslint/typescript-eslint at chore/lerna-4', + ), 'chore/lerna-4'); + + // Sub folder + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/tree/master/docs', + 'typescript-eslint/docs at master · typescript-eslint/typescript-eslint', + ), 'master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/tree/chore/lerna-4/docs', + 'typescript-eslint/docs at chore/lerna-4 · typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + // Sub sub folder + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/tree/master/docs/getting-started', + 'typescript-eslint/docs/getting-started at master · typescript-eslint/typescript-eslint', + ), 'master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/tree/chore/lerna-4/docs/getting-started', + 'typescript-eslint/docs/getting-started at chore/lerna-4 · typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + // File + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/README.md', + 'typescript-eslint/README.md at master · typescript-eslint/typescript-eslint', + ), 'master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/blob/chore/lerna-4/docs/getting-started/README.md', + 'typescript-eslint/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + // Editing file + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/edit/master/docs/getting-started/README.md', + 'Editing typescript-eslint/README.md at master · typescript-eslint/typescript-eslint', + ), 'master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/edit/chore/lerna-4/docs/getting-started/README.md', + 'Editing typescript-eslint/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + // Blame + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/blame/master/docs/getting-started/README.md', + 'typescript-eslint/docs/getting-started/README.md at master · typescript-eslint/typescript-eslint', + ), 'master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/blame/chore/lerna-4/docs/getting-started/README.md', + 'typescript-eslint/docs/getting-started/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + // Commits + navigateToBranch('master'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/commits/master/docs/getting-started/README.md', + 'History for docs/getting-started/README.md - typescript-eslint/typescript-eslint', + ), 'master'); + + navigateToBranch('chore/lerna-4'); + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/commits/chore/lerna-4/docs/getting-started/README.md', + 'History for docs/getting-started/README.md - typescript-eslint/typescript-eslint', + ), 'chore/lerna-4'); + + navigateToBranch('this/branch/has/many/slashes'); + assert.equal(getCurrentGitRef( + '/yakov116/TestR/commits/this/branch/has/many/slashes', + 'Commits · yakov116/TestR', + ), 'this/branch/has/many/slashes'); + + // Single commit + assert.equal(getCurrentGitRef( + '/typescript-eslint/typescript-eslint/commit/795fd1c529ee58e97283c9ddf8463703517b50ab', + 'chore: add markdownlint (#1889) · typescript-eslint/typescript-eslint@795fd1c', + ), '795fd1c529ee58e97283c9ddf8463703517b50ab'); + + // Branch includes period + assert.equal(getCurrentGitRef( + '/anggrayudi/SimpleStorage/tree/release/0.8.0', + 'anggrayudi/SimpleStorage at release/0.8.0', + ), 'release/0.8.0'); + + assert.equal(getCurrentGitRef( + '/ksh-code/repository/tree/h.l.o.o', + 'ksh-code/repository at h.l.o.o', + ), 'h.l.o.o'); +}); diff --git a/source/github-helpers/get-current-git-ref.ts b/source/github-helpers/get-current-git-ref.ts new file mode 100644 index 00000000..4eefbabb --- /dev/null +++ b/source/github-helpers/get-current-git-ref.ts @@ -0,0 +1,32 @@ +import {getCurrentBranchFromFeed} from './index.js'; + +const typesWithGitRef = new Set(['tree', 'blob', 'blame', 'edit', 'commit', 'commits', 'compare']); +const titleWithGitRef = / at (?<branch>[.\w-/]+)( · [\w-]+\/[\w-]+)?$/i; + +/** This only works with URL and page title. Must not be async because it's used by GitHubURL */ +export default function getCurrentGitRef(pathname = location.pathname, title = document.title): string | undefined { + if (!pathname.startsWith('/')) { + throw new TypeError(`Expected pathname starting with /, got "${pathname}"`); + } + + const [type, gitRefIfNonSlashed] = pathname.split('/').slice(3); + if (!type || !typesWithGitRef.has(type)) { + // Root; or piece of information not applicable to the page + return; + } + + // Slashed branches on `commits` + if (type === 'commits') { + return getCurrentBranchFromFeed()!; + } + + // Slashed branches on `blob` and `tree` + const parsedTitle = titleWithGitRef.exec(title); + if (parsedTitle) { + return parsedTitle.groups!.branch; + } + + // Couldn't ensure it's not slashed, so we'll return the first piece whether correct or not + // TODO: extract from `ref-selector` if available https://github.com/refined-github/refined-github/issues/6557 + return gitRefIfNonSlashed; +} diff --git a/source/github-helpers/get-default-branch.ts b/source/github-helpers/get-default-branch.ts index 34f5bd11..efbbdd4c 100644 --- a/source/github-helpers/get-default-branch.ts +++ b/source/github-helpers/get-default-branch.ts @@ -1,44 +1,24 @@ import cache from 'webext-storage-cache'; -import select from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; import * as api from './api.js'; -import {getRepo, getCurrentBranchFromFeed} from './index.js'; +import {getRepo} from './index.js'; import {branchSelector} from './selectors.js'; -// This regex should match all of these combinations: -// "This branch is even with master." -// "This branch is 1 commit behind master." -// "This branch is 1 commit ahead of master." -// "This branch is 1 commit ahead, 27 commits behind master." -const branchInfoRegex = /([^ ]+)\.$/; +const isCurrentRepo = ({nameWithOwner}: pageDetect.RepositoryInfo): boolean => Boolean(getRepo()?.nameWithOwner === nameWithOwner); // DO NOT use optional arguments/defaults in "cached functions" because they can't be memoized effectively // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1864 -const _getDefaultBranch = cache.function('default-branch', async function (repository: pageDetect.RepositoryInfo): Promise<string> { - if (arguments.length === 0 || JSON.stringify(repository) === JSON.stringify(getRepo())) { - if (pageDetect.isRepoHome()) { - const branchPicker = await elementReady(branchSelector); - if (branchPicker) { - return branchPicker.title === 'Switch branches or tags' - ? branchPicker.textContent!.trim() - : branchPicker.title; - } - } - - const defaultBranch = getCurrentBranchFromFeed(); - if (defaultBranch) { - return defaultBranch; - } - - if (!pageDetect.isForkedRepo()) { - // We can find the name in the infobar, available in folder views - const branchInfo = select('.branch-infobar')?.textContent!.trim(); - const defaultBranch = branchInfoRegex.exec(branchInfo!)?.[1]; - if (defaultBranch) { - return defaultBranch; - } +const _getDefaultBranch = cache.function('default-branch', async (repository: pageDetect.RepositoryInfo): Promise<string> => { + // TODO: extract from `ref-selector` if available https://github.com/refined-github/refined-github/issues/6557 + if (isCurrentRepo(repository) && ['', 'commits'].includes(repository.path)) { + // We're on the default branch, so we can extract it from the current page. This usually happens on the pages: + // @example /user/repo + // @example /user/repo/commits (without further path) + const branchPicker = await elementReady(branchSelector); + if (branchPicker) { + return branchPicker.textContent!.trim(); } } diff --git a/source/github-helpers/github-url.ts b/source/github-helpers/github-url.ts index 2f592f50..e31b307c 100644 --- a/source/github-helpers/github-url.ts +++ b/source/github-helpers/github-url.ts @@ -1,4 +1,4 @@ -import {getCurrentCommittish} from './index.js'; +import getCurrentGitRef from './get-current-git-ref.js'; export default class GitHubURL { user = ''; @@ -37,7 +37,7 @@ export default class GitHubURL { const filePath = ambiguousReference.slice(1).join('/'); - const currentBranch = getCurrentCommittish(); + const currentBranch = getCurrentGitRef(); const currentBranchSections = currentBranch?.split('/'); if ( !currentBranch // Current branch could not be determined (1/2) diff --git a/source/github-helpers/index.ts b/source/github-helpers/index.ts index c38acc83..883b10df 100644 --- a/source/github-helpers/index.ts +++ b/source/github-helpers/index.ts @@ -20,13 +20,12 @@ export function getConversationNumber(): number | undefined { return undefined; } -export function getCurrentBranchFromFeed(): string | void { - // Not `isRepoCommitList` because this works exclusively on the default branch - if (getRepo()!.path !== 'commits') { - return; +export function getCurrentBranchFromFeed(): string { + const feedLink = select('link[type="application/atom+xml"]'); + if (!feedLink) { + throw new Error('getCurrentBranchFromFeed() is only available on commit lists'); } - const feedLink = select('link[type="application/atom+xml"]')!; return new URL(feedLink.href) .pathname .split('/') @@ -35,45 +34,6 @@ export function getCurrentBranchFromFeed(): string | void { .replace(/\.atom$/, ''); } -const typesWithCommittish = new Set(['tree', 'blob', 'blame', 'edit', 'commit', 'commits', 'compare']); -const titleWithCommittish = / at (?<branch>[.\w-/]+)( · [\w-]+\/[\w-]+)?$/i; -export const getCurrentCommittish = (pathname = location.pathname, title = document.title): string | undefined => { - if (!pathname.startsWith('/')) { - throw new TypeError(`Expected pathname starting with /, got "${pathname}"`); - } - - const [type, unslashedCommittish] = pathname.split('/').slice(3); - if (!type || !typesWithCommittish.has(type)) { - // Root; or piece of information not applicable to the page - return; - } - - // Handle slashed branches in commits pages - if (type === 'commits') { - if (!unslashedCommittish) { - return getCurrentBranchFromFeed()!; - } - - const branchAndFilepath = pathname.split('/').slice(4).join('/'); - - // List of all commits of current branch (no filename) - if (title.startsWith('Commits · ')) { - return branchAndFilepath; - } - - // List of commits touching a particular file ("History") - const filepath = /^History for ([^ ]+) - /.exec(title)![1]; - return branchAndFilepath.slice(0, branchAndFilepath.lastIndexOf('/' + filepath)); - } - - const parsedTitle = titleWithCommittish.exec(title); - if (parsedTitle) { - return parsedTitle.groups!.branch; - } - - return unslashedCommittish; -}; - export const isMac = navigator.userAgent.includes('Macintosh'); type Not<Yes, Not> = Yes extends Not ? never : Yes; @@ -139,7 +99,7 @@ const cachePerPage = { /** Is tag or commit, with elementReady */ export const isPermalink = mem(async () => { - // No need for getCurrentCommittish(), it's a simple and exact check + // No need for getCurrentGitRef(), it's a simple and exact check if (/^[\da-f]{40}$/.test(location.pathname.split('/')[4])) { // It's a commit return true; diff --git a/source/github-helpers/is-default-branch.ts b/source/github-helpers/is-default-branch.ts new file mode 100644 index 00000000..82ef0be1 --- /dev/null +++ b/source/github-helpers/is-default-branch.ts @@ -0,0 +1,13 @@ +import getDefaultBranch from './get-default-branch.js'; +import getCurrentGitRef from './get-current-git-ref.js'; + +/** Detects if the current view is on the default branch. To be used on file/folder/commit lists */ +export default async function isDefaultBranch(): Promise<boolean> { + const currentBranch = getCurrentGitRef(); + if (!currentBranch) { + // This happens on the repo root OR on views that are not branch-specific (like isIssue) + return true; + } + + return currentBranch === await getDefaultBranch(); +} |