/
deep-reblame.tsx
121 lines (106 loc) · 3.84 KB
/
deep-reblame.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import './deep-reblame.css';
import mem from 'mem';
import React from 'dom-chef';
import select from 'select-dom';
import delegate from 'delegate-it';
import VersionIcon from 'octicon/versions.svg';
import * as pageDetect from 'github-url-detection';
import features from '.';
import * as api from '../github-helpers/api';
import GitHubURL from '../github-helpers/github-url';
import LoadingIcon from '../github-helpers/icon-loading';
import {getRepoGQL} from '../github-helpers';
import looseParseInt from '../helpers/loose-parse-int';
const getPullRequestBlameCommit = mem(async (commit: string, prNumber: number, currentFilename: string): Promise<string> => {
const {repository} = await api.v4(`
repository(${getRepoGQL()}) {
file: object(expression: "${commit}:${currentFilename}") {
id
}
object(expression: "${commit}") {
... on Commit {
associatedPullRequests(last: 1) {
nodes {
number
mergeCommit {
oid
}
commits(last: 1) {
nodes {
commit {
oid
}
}
}
}
}
}
}
}
`);
const associatedPR = repository.object.associatedPullRequests.nodes[0];
if (!associatedPR || associatedPR.number !== prNumber || associatedPR.mergeCommit.oid !== commit) {
throw new Error('The PR linked in the title didn’t create this commit');
}
if (!repository.file) {
throw new Error('The file was renamed and Refined GitHub can’t find it');
}
return associatedPR.commits.nodes[0].commit.oid;
});
async function redirectToBlameCommit(event: delegate.Event<MouseEvent, HTMLAnchorElement | HTMLButtonElement>): Promise<void> {
const blameElement = event.delegateTarget;
if (blameElement instanceof HTMLAnchorElement && !event.altKey) {
return; // Unmodified click on regular link: let it proceed
}
event.preventDefault();
blameElement.blur(); // Hide tooltip after click, it’s shown on :focus
const blameHunk = blameElement.closest('.blame-hunk')!;
const prNumber = looseParseInt(select('.issue-link', blameHunk)!.textContent!);
const prCommit = select<HTMLAnchorElement>('a.message', blameHunk)!.pathname.split('/').pop()!;
const blameUrl = new GitHubURL(location.href);
const spinner = <LoadingIcon className="mr-2"/>;
blameElement.firstElementChild!.replaceWith(spinner);
try {
blameUrl.branch = await getPullRequestBlameCommit(prCommit, prNumber, blameUrl.filePath);
blameUrl.hash = 'L' + select('.js-line-number', blameHunk)!.textContent!;
location.href = String(blameUrl);
} catch (error) {
spinner.replaceWith(<VersionIcon/>);
alert(error.message);
}
}
function init(): void | false {
const pullRequests = select.all('[data-hovercard-type="pull_request"]');
if (pullRequests.length === 0) {
return false;
}
delegate(document, '.rgh-deep-reblame', 'click', redirectToBlameCommit);
for (const pullRequest of pullRequests) {
const hunk = pullRequest.closest('.blame-hunk')!;
const reblameLink = select('.reblame-link', hunk);
if (reblameLink) {
reblameLink.setAttribute('aria-label', 'View blame prior to this change. Hold `Alt` to extract commits from this PR first');
reblameLink.classList.add('rgh-deep-reblame');
} else {
select('.blob-reblame', hunk)!.append(
<button
type="button"
aria-label="View blame prior to this change (extracts commits from this PR first)"
className="reblame-link btn-link no-underline tooltipped tooltipped-e d-inline-block pr-1 rgh-deep-reblame"
>
<VersionIcon/>
</button>
);
}
}
}
void features.add({
id: __filebasename,
description: 'When exploring blames, `Alt`-clicking the “Reblame” buttons will extract the associated PR’s commits first, instead of treating the commit a single change.',
screenshot: 'https://user-images.githubusercontent.com/16872793/77248541-8e3f2180-6c10-11ea-91d4-221ccc0ecebb.png'
}, {
include: [
pageDetect.isBlame
],
init
});