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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
import React from 'dom-chef';
import select from 'select-dom';
import delegate, {DelegateEvent} from 'delegate-it';
import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
import * as api from '../github-helpers/api.js';
import showToast from '../github-helpers/toast.js';
import {getConversationNumber} from '../github-helpers/index.js';
import {getBranches} from '../github-helpers/pr-branches.js';
import getPrInfo from '../github-helpers/get-pr-info.js';
async function getBaseReference(): Promise<string> {
const {base} = getBranches();
const {baseRefOid} = await getPrInfo(base.relative);
return baseRefOid;
}
async function getHeadReference(): Promise<string> {
// Get the sha of the latest commit to the PR, required to create a new commit
const {repository} = await api.v4(`
repository() { # Cache buster ${Math.random()}
pullRequest(number: ${getConversationNumber()!}) {
headRefOid
}
}
`);
return repository.pullRequest.headRefOid;
}
async function getFile(filePath: string): Promise<{isTruncated: boolean; text: string} | undefined> {
const {repository} = await api.v4(`
repository() {
file: object(expression: "${await getBaseReference()}:${filePath}") {
... on Blob {
isTruncated
text
}
}
}
`);
return repository.file;
}
async function discardChanges(progress: (message: string) => void, filePath: string): Promise<void> {
const file = await getFile(filePath);
if (file?.isTruncated) {
throw new Error('File too big, you’ll have to use git');
}
// Only possible if `highlight-deleted-and-added-files-in-diffs` is broken or disabled
const isNewFile = !file;
const change = isNewFile ? `
deletions: [
{
path: "${filePath}"
}
]
` : `
additions: [
{
path: "${filePath}",
contents: "${btoa(unescape(encodeURIComponent(file.text)))}"
}
]
`;
const {nameWithOwner, branch: prBranch} = getBranches().head;
progress('Committing…');
await api.v4(`mutation {
createCommitOnBranch(input: {
branch: {
repositoryNameWithOwner: "${nameWithOwner}",
branchName: "${prBranch}"
},
expectedHeadOid: "${await getHeadReference()}",
fileChanges: {
${change}
},
message: {
headline: "Discard changes to ${filePath}"
}
}) {
commit {
oid
}
}
}`);
}
async function handleClick(event: DelegateEvent<MouseEvent, HTMLButtonElement>): Promise<void> {
const menuItem = event.delegateTarget;
try {
const filePath = menuItem.closest<HTMLDivElement>('[data-path]')!.dataset.path!;
await showToast(async progress => discardChanges(progress!, filePath), {
message: 'Loading info…',
doneMessage: 'Changes discarded',
});
// Hide file from view
menuItem.closest('.file')!.remove();
} catch (error) {
features.log.error(import.meta.url, error);
}
}
function handleMenuOpening({delegateTarget: dropdown}: DelegateEvent): void {
const editFile = select('a[aria-label^="Change this"]', dropdown);
if (!editFile || select.exists('.rgh-restore-file', dropdown)) {
return;
}
if (editFile.closest('.file-header')!.querySelector('[aria-label="File added"]')) {
// The file is new. "Discarding changes" means deleting it, which is already possible.
// Depends on `highlight-deleted-and-added-files-in-diffs`.
return;
}
editFile.after(
<button
className="pl-5 dropdown-item btn-link rgh-restore-file"
style={{whiteSpace: 'pre-wrap'}}
role="menuitem"
type="button"
>
Discard changes
</button>,
);
}
function init(signal: AbortSignal): void {
// `capture: true` required to be fired before GitHub's handlers
delegate('.file-header .js-file-header-dropdown', 'toggle', handleMenuOpening, {capture: true, signal});
delegate('.rgh-restore-file', 'click', handleClick, {capture: true, signal});
}
void features.add(import.meta.url, {
include: [
pageDetect.isPRFiles,
pageDetect.isPRCommit,
],
init,
});
/*
Test URLs:
https://github.com/refined-github/sandbox/pull/16/files
*/
|