Skip to content

Commit f41d7f2

Browse files
authored
Improve branch getter function (refined-github#4193)
1 parent 936eedb commit f41d7f2

10 files changed

Lines changed: 141 additions & 49 deletions

source/features/default-branch-button.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import features from '.';
77
import GitHubURL from '../github-helpers/github-url';
88
import {groupButtons} from '../github-helpers/group-buttons';
99
import getDefaultBranch from '../github-helpers/get-default-branch';
10-
import {getCurrentBranch} from '../github-helpers';
10+
import {getCurrentCommittish} from '../github-helpers';
1111

1212
async function init(): Promise<false | void> {
1313
const branchSelector = await elementReady<HTMLElement>('[data-hotkey="w"]');
@@ -17,7 +17,7 @@ async function init(): Promise<false | void> {
1717
}
1818

1919
const defaultBranch = await getDefaultBranch();
20-
const currentBranch = getCurrentBranch()!;
20+
const currentBranch = getCurrentCommittish();
2121

2222
// Don't show the button if we’re already on the default branch
2323
if (defaultBranch === currentBranch) {

source/features/enable-file-links-in-compare-view.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ import * as pageDetect from 'github-url-detection';
66

77
import features from '.';
88
import GitHubURL from '../github-helpers/github-url';
9-
import {getCurrentBranch, getPRHeadRepo} from '../github-helpers';
109

1110
/** Rebuilds the "View file" link because it points to the base repo and to the commit, instead of the head repo and its branch */
1211
function handlePRMenuOpening({delegateTarget: dropdown}: delegate.Event): void {
1312
dropdown.classList.add('rgh-actionable-link'); // Mark this as processed
13+
14+
const [nameWithOwner, headBranch] = select('.head-ref')!.title.split(':');
1415
const filePath = dropdown.closest('[data-path]')!.getAttribute('data-path')!;
1516

16-
const viewFile = select('a[data-ga-click^="View file"]', dropdown)!;
17-
viewFile.pathname = [getPRHeadRepo()!.nameWithOwner, 'blob', getCurrentBranch()!, filePath].join('/'); // Do not replace with `GitHubURL` #3152 #3111 #2595
17+
select('a[data-ga-click^="View file"]', dropdown)!
18+
.pathname = [nameWithOwner, 'blob', headBranch, filePath].join('/'); // Do not replace with `GitHubURL` #3152 #3111 #2595
1819
}
1920

2021
function handleCompareMenuOpening({delegateTarget: dropdown}: delegate.Event): void {

source/features/git-checkout-pr.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import {ClippyIcon} from '@primer/octicons-react';
55
import * as pageDetect from 'github-url-detection';
66

77
import features from '.';
8-
import {getCurrentBranch, getPRHeadRepo, getRepo, getUsername} from '../github-helpers';
8+
import {getRepo, getUsername} from '../github-helpers';
99

1010
// Logic explained in https://github.com/sindresorhus/refined-github/pull/3596#issuecomment-720910840
1111
function getRemoteName(): string | undefined {
12-
const author = getPRHeadRepo()!.owner;
12+
const [author] = select('.head-ref')!.title.split('/');
1313
if (author === getUsername()) {
1414
return; // `origin`, don't add remote
1515
}
@@ -31,6 +31,8 @@ const connectionType = {
3131
};
3232

3333
function checkoutOption(remote?: string, remoteType?: 'HTTPS' | 'SSH'): JSX.Element {
34+
const [nameWithOwner, headBranch] = select('.head-ref')!.title.split(':');
35+
const [owner] = nameWithOwner.split('/');
3436
return (
3537
<>
3638
{remote && <p className="text-gray color-text-secondary text-small my-1">{remoteType}</p>}
@@ -51,9 +53,9 @@ function checkoutOption(remote?: string, remoteType?: 'HTTPS' | 'SSH'): JSX.Elem
5153
className="copyable-terminal-content"
5254
>
5355
<span className="user-select-contain">
54-
{remote && `git remote add ${remote} ${connectionType[remoteType!]}${getPRHeadRepo()!.nameWithOwner}.git\n`}
55-
git fetch {remote ?? 'origin'} {getCurrentBranch()}{'\n'}
56-
git switch {remote && `--track ${getPRHeadRepo()!.owner}/`}{getCurrentBranch()}
56+
{remote && `git remote add ${remote} ${connectionType[remoteType!]}${nameWithOwner}.git\n`}
57+
git fetch {remote ?? 'origin'} {headBranch}{'\n'}
58+
git switch {remote && `--track ${owner}/`}{headBranch}
5759
</span>
5860
</pre>
5961
</div>

source/features/latest-tag-button.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import pluralize from '../helpers/pluralize';
1111
import GitHubURL from '../github-helpers/github-url';
1212
import {groupButtons} from '../github-helpers/group-buttons';
1313
import getDefaultBranch from '../github-helpers/get-default-branch';
14-
import {buildRepoURL, getCurrentBranch, getLatestVersionTag, getRepo} from '../github-helpers';
14+
import {buildRepoURL, getCurrentCommittish, getLatestVersionTag, getRepo} from '../github-helpers';
1515

1616
interface RepoPublishState {
1717
latestTag: string | false;
@@ -83,7 +83,6 @@ async function init(): Promise<false | void> {
8383
return false;
8484
}
8585

86-
const currentBranch = getCurrentBranch()!;
8786
const url = new GitHubURL(location.href);
8887
url.assign({
8988
route: url.route || 'tree', // If route is missing, it's a repo root
@@ -96,7 +95,10 @@ async function init(): Promise<false | void> {
9695
</a>
9796
);
9897

99-
(await elementReady('#branch-select-menu', {waitForChildren: false}))!.closest('.position-relative')!.after(link);
98+
const branchSelector = await elementReady('#branch-select-menu', {waitForChildren: false});
99+
branchSelector!.closest('.position-relative')!.after(link);
100+
101+
const currentBranch = getCurrentCommittish();
100102
if (currentBranch !== latestTag) {
101103
link.append(' ', <span className="css-truncate-target">{latestTag}</span>);
102104
}

source/features/more-dropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {DiffIcon, GitBranchIcon, HistoryIcon, PackageIcon} from '@primer/octicon
88
import features from '.';
99
import {appendBefore} from '../helpers/dom-utils';
1010
import getDefaultBranch from '../github-helpers/get-default-branch';
11-
import {buildRepoURL, getCurrentBranch} from '../github-helpers';
11+
import {buildRepoURL, getCurrentCommittish} from '../github-helpers';
1212

1313
function createDropdown(): void {
1414
// Markup copied from native GHE dropdown
@@ -59,7 +59,7 @@ async function init(): Promise<void> {
5959
'.UnderlineNav-body'
6060
].join());
6161

62-
const reference = getCurrentBranch() ?? await getDefaultBranch();
62+
const reference = getCurrentCommittish() ?? await getDefaultBranch();
6363
const compareUrl = buildRepoURL('compare', reference);
6464
const commitsUrl = buildRepoURL('commits', reference);
6565
const branchesUrl = buildRepoURL('branches');

source/features/restore-file.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as pageDetect from 'github-url-detection';
88
import features from '.';
99
import * as api from '../github-helpers/api';
1010
import fetchDom from '../helpers/fetch-dom';
11-
import {getConversationNumber, getCurrentBranch, getPRHeadRepo} from '../github-helpers';
11+
import {getConversationNumber} from '../github-helpers';
1212

1313
function showError(menuItem: HTMLButtonElement, error: string): void {
1414
menuItem.disabled = true;
@@ -50,7 +50,8 @@ async function commitFileContent(menuItem: Element, content: string, filePath: s
5050
// Check if file was deleted by PR
5151
if (menuItem.closest('[data-file-deleted="true"]')) {
5252
menuItem.textContent = 'Undeleting…';
53-
pathname = `/${getPRHeadRepo()!.nameWithOwner}/new/${getCurrentBranch()!}?filename=${filePath}`;
53+
const [nameWithOwner, headBranch] = select('.head-ref')!.title.split(':');
54+
pathname = `/${nameWithOwner}/new/${headBranch}?filename=${filePath}`;
5455
} else {
5556
menuItem.textContent = 'Committing…';
5657
}

source/github-helpers/get-default-branch.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import select from 'select-dom';
33
import * as pageDetect from 'github-url-detection';
44

55
import * as api from './api';
6-
import {getRepo, getCurrentBranch} from '.';
6+
import {getRepo} from '.';
77

88
// This regex should match all of these combinations:
99
// "This branch is even with master."
@@ -23,7 +23,10 @@ const getDefaultBranch = cache.function(async function (repository?: pageDetect.
2323

2424
if (arguments.length === 0 || JSON.stringify(repository) === JSON.stringify(getRepo())) {
2525
if (pageDetect.isRepoHome()) {
26-
return getCurrentBranch()!;
26+
const currentBranch = select('#branch-select-menu [data-menu-button]');
27+
if (currentBranch) { // If missing, it'll default to the API call
28+
return currentBranch.textContent!;
29+
}
2730
}
2831

2932
if (!pageDetect.isForkedRepo()) {

source/github-helpers/github-url.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {getCurrentBranch} from '.';
1+
import {getCurrentCommittish} from '.';
22

33
export default class GitHubURL {
44
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/26792
@@ -46,13 +46,11 @@ export default class GitHubURL {
4646

4747
const filePath = ambiguousReference.slice(1).join('/');
4848

49-
const currentBranch = getCurrentBranch();
50-
if (!currentBranch) {
51-
throw new Error('GitHubURL can only be used on pages with a branch/reference.');
52-
}
53-
54-
const currentBranchSections = currentBranch.split('/');
49+
const currentBranch = getCurrentCommittish();
50+
const currentBranchSections = currentBranch?.split('/');
5551
if (
52+
!currentBranch || // Current branch could not be determined (1/2)
53+
!currentBranchSections || // Current branch could not be determined (2/2)
5654
ambiguousReference.length === 1 || // Ref has no slashes
5755
currentBranchSections.length === 1 // Current branch has no slashes
5856
) {

source/github-helpers/index.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ export const getConversationNumber = (): string | undefined => {
1616
return undefined;
1717
};
1818

19-
/**
20-
Tested on isRepoTree, isBlame, isSingleFile, isEditFile, isSingleCommit, isCommitList, isCompare. Subtly incompatible with isPR
21-
Example tag content on public repositories: https://github.com/sindresorhus/refined-github/commits/branch-or-commit/even/with/slashes.atom
22-
Example tag content on private repositories https://github.com/private/private/commits/master.atom?token=AEAXKWNRHXA2XJ2ZWCMGUUN44LM62
23-
*/
24-
export const getCurrentBranch = (): string | undefined => {
25-
// .last needed for #2799
26-
const feedLink = select.last('link[type="application/atom+xml"]');
27-
// The feedLink is not available on `isIssue` #3641
28-
if (!feedLink) {
19+
const typesWithCommittish = new Set(['tree', 'blob', 'blame', 'edit', 'commit', 'commits', 'compare']);
20+
const titleWithCommittish = / at (?<branch>[\w-/]+)( · [\w-]+\/[\w-]+)?$/i;
21+
export const getCurrentCommittish = (pathname = location.pathname, title = document.title): string | undefined => {
22+
if (!pathname.startsWith('/')) {
23+
throw new TypeError(`Expected pathname starting with /, got "${pathname}"`);
24+
}
25+
26+
const [, _user, _repo, type, unslashedCommittish] = pathname.split('/');
27+
if (!type || !typesWithCommittish.has(type)) {
28+
// Root; or piece of information not applicable to the page
2929
return;
3030
}
3131

32-
return new URL(feedLink.href)
33-
.pathname
34-
.split('/')
35-
.slice(4) // Drops the initial /user/repo/route/ part
36-
.join('/')
37-
.replace(/\.atom$/, '');
32+
const parsedTitle = titleWithCommittish.exec(title);
33+
if (parsedTitle) {
34+
return parsedTitle.groups!.branch;
35+
}
36+
37+
return unslashedCommittish;
3838
};
3939

4040
export const isFirefox = navigator.userAgent.includes('Firefox/');
@@ -51,11 +51,6 @@ export const buildRepoURL = (...pathParts: Array<string | number> & {0: string})
5151
return [location.origin, getRepo()?.nameWithOwner, ...pathParts].join('/');
5252
};
5353

54-
export const getPRHeadRepo = (): ReturnType<typeof getRepo> => {
55-
const headLink = select('.commit-ref.head-ref a');
56-
return getRepo(headLink);
57-
};
58-
5954
export function getForkedRepo(): string | undefined {
6055
return select('meta[name="octolytics-dimension-repository_parent_nwo"]')?.content;
6156
}
@@ -126,7 +121,7 @@ export function upperCaseFirst(input: string): string {
126121

127122
/** Is tag or commit, with elementReady */
128123
export async function isPermalink(): Promise<boolean> {
129-
if (/^[\da-f]{40}$/.test(getCurrentBranch()!)) {
124+
if (/^[\da-f]{40}$/.test(getCurrentCommittish()!)) {
130125
// It's a commit
131126
return true;
132127
}

test/helpers.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
preventPrCommitLinkLoss,
1212
preventPrCompareLinkLoss,
1313
prCommitUrlRegex,
14-
prCompareUrlRegex
14+
prCompareUrlRegex,
15+
getCurrentCommittish
1516
} from '../source/github-helpers';
1617

1718
test('getConversationNumber', t => {
@@ -207,3 +208,92 @@ test('preventPrCommitLinkLoss', t => {
207208
'It should ignore Markdown links'
208209
);
209210
});
211+
212+
// The titles supplied here listed here are real, not guessed, except the error tester
213+
test('getCurrentCommittish', t => {
214+
// Error testing
215+
t.is(getCurrentCommittish(
216+
'/',
217+
'some page title'
218+
), undefined, 'It should never throw with valid input');
219+
t.throws(() => getCurrentCommittish(
220+
'https://github.com',
221+
'github.com'
222+
));
223+
224+
// Root
225+
t.is(getCurrentCommittish(
226+
'/typescript-eslint/typescript-eslint',
227+
'typescript-eslint/typescript-eslint: Monorepo for all the tooling which enables ESLint to support TypeScript'
228+
), undefined);
229+
t.is(getCurrentCommittish(
230+
'/typescript-eslint/typescript-eslint/tree/chore/lerna-4',
231+
'typescript-eslint/typescript-eslint at chore/lerna-4'
232+
), 'chore/lerna-4');
233+
234+
// Sub folder
235+
t.is(getCurrentCommittish(
236+
'/typescript-eslint/typescript-eslint/tree/master/docs',
237+
'typescript-eslint/docs at master · typescript-eslint/typescript-eslint'
238+
), 'master');
239+
t.is(getCurrentCommittish(
240+
'/typescript-eslint/typescript-eslint/tree/chore/lerna-4/docs',
241+
'typescript-eslint/docs at chore/lerna-4 · typescript-eslint/typescript-eslint'
242+
), 'chore/lerna-4');
243+
244+
// Sub sub folder
245+
t.is(getCurrentCommittish(
246+
'/typescript-eslint/typescript-eslint/tree/master/docs/getting-started',
247+
'typescript-eslint/docs/getting-started at master · typescript-eslint/typescript-eslint'
248+
), 'master');
249+
t.is(getCurrentCommittish(
250+
'/typescript-eslint/typescript-eslint/tree/chore/lerna-4/docs/getting-started',
251+
'typescript-eslint/docs/getting-started at chore/lerna-4 · typescript-eslint/typescript-eslint'
252+
), 'chore/lerna-4');
253+
254+
// File
255+
t.is(getCurrentCommittish(
256+
'/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/README.md',
257+
'typescript-eslint/README.md at master · typescript-eslint/typescript-eslint'
258+
), 'master');
259+
t.is(getCurrentCommittish(
260+
'/typescript-eslint/typescript-eslint/blob/chore/lerna-4/docs/getting-started/README.md',
261+
'typescript-eslint/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint'
262+
), 'chore/lerna-4');
263+
264+
// Editing file
265+
t.is(getCurrentCommittish(
266+
'/typescript-eslint/typescript-eslint/edit/master/docs/getting-started/README.md',
267+
'Editing typescript-eslint/README.md at master · typescript-eslint/typescript-eslint'
268+
), 'master');
269+
t.is(getCurrentCommittish(
270+
'/typescript-eslint/typescript-eslint/edit/chore/lerna-4/docs/getting-started/README.md',
271+
'Editing typescript-eslint/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint'
272+
), 'chore/lerna-4');
273+
274+
// Blame
275+
t.is(getCurrentCommittish(
276+
'/typescript-eslint/typescript-eslint/blame/master/docs/getting-started/README.md',
277+
'typescript-eslint/docs/getting-started/README.md at master · typescript-eslint/typescript-eslint'
278+
), 'master');
279+
t.is(getCurrentCommittish(
280+
'/typescript-eslint/typescript-eslint/blame/chore/lerna-4/docs/getting-started/README.md',
281+
'typescript-eslint/docs/getting-started/README.md at chore/lerna-4 · typescript-eslint/typescript-eslint'
282+
), 'chore/lerna-4');
283+
284+
// Commits
285+
t.is(getCurrentCommittish(
286+
'/typescript-eslint/typescript-eslint/commits/master/docs/getting-started/README.md',
287+
'History for docs/getting-started/README.md - typescript-eslint/typescript-eslint'
288+
), 'master');
289+
t.is(getCurrentCommittish(
290+
'/typescript-eslint/typescript-eslint/commits/chore/lerna-4/docs/getting-started/README.md',
291+
'History for docs/getting-started/README.md - typescript-eslint/typescript-eslint'
292+
), 'chore'); // Wrong, but
293+
294+
// Single commit
295+
t.is(getCurrentCommittish(
296+
'/typescript-eslint/typescript-eslint/commit/795fd1c529ee58e97283c9ddf8463703517b50ab',
297+
'chore: add markdownlint (#1889) · typescript-eslint/typescript-eslint@795fd1c'
298+
), '795fd1c529ee58e97283c9ddf8463703517b50ab');
299+
});

0 commit comments

Comments
 (0)