summaryrefslogtreecommitdiff
path: root/source/features/pr-branches.tsx
blob: e23990dcda7adb45af71503460bafcb41fbf668e (plain) (blame)
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 * as pageDetect from 'github-url-detection';
import PullRequestIcon from 'octicon/git-pull-request.svg';

import features from '.';
import * as api from '../github-helpers/api';
import {botSelectors} from './dim-bots';
import getDefaultBranch from '../github-helpers/get-default-branch';
import {getRepositoryInfo, getRepoGQL} from '../github-helpers';

type RepositoryReference = {
	owner: string;
	branchExists: boolean;
	url?: string;
	label: string;
};

type BranchInfo = {
	baseRef: string;
	baseRefName: string;
	headRef: string;
	headOwner: {
		login: string;
	};
	headRefName: string;
	headRepository?: {
		url: string;
	};
};

function normalizeBranchInfo(data: BranchInfo): {
	base?: RepositoryReference;
	head?: RepositoryReference;
} {
	const currentRepository = getRepositoryInfo();

	const base = {} as RepositoryReference; // eslint-disable-line @typescript-eslint/consistent-type-assertions
	base.branchExists = Boolean(data.baseRef);
	base.label = data.baseRefName;
	if (base.branchExists) {
		base.url = `/${currentRepository.owner!}/${currentRepository.name!}/tree/${data.baseRefName}`;
	}

	const head = {} as RepositoryReference; // eslint-disable-line @typescript-eslint/consistent-type-assertions
	head.branchExists = Boolean(data.headRef);
	head.owner = data.headOwner.login;
	if (data.headOwner.login === currentRepository.owner) {
		head.label = data.headRefName;
	} else {
		head.label = `${data.headOwner.login}:${data.headRefName}`;
	}

	if (head.branchExists) { // If the branch hasn't been deleted
		head.url = `${data.headRepository!.url}/tree/${data.headRefName}`;
	} else if (data.headRepository) { // If the repo hasn't been deleted
		head.url = data.headRepository.url;
	}

	return {base, head};
}

function buildQuery(issueIds: string[]): string {
	return `
		repository(${getRepoGQL()}) {
			${issueIds.map(id => `
				${id}: pullRequest(number: ${id.replace(/\D/g, '')}) {
					baseRef {id}
					headRef {id}
					baseRefName
					headRefName
					headRepository {url}
					headOwner: headRepositoryOwner {login}
				}
			`).join('\n')}
		}
	`;
}

function createLink(reference: RepositoryReference): HTMLSpanElement {
	return (
		<span
			className="commit-ref css-truncate user-select-contain mb-n1"
			style={(reference.branchExists ? {} : {textDecoration: 'line-through'})}
		>
			{
				reference.url ?
					<a title={(reference.branchExists ? reference.label : 'Deleted')} href={reference.url}>
						{reference.label}
					</a> :
					<span className="unknown-repo">unknown repository</span>
			}
		</span>
	);
}

async function init(): Promise<false | void> {
	const prLinks = select.all('.js-issue-row .js-navigation-open[data-hovercard-type="pull_request"]')
		// Exclude bots
		.filter(link => !link.parentElement?.querySelector(botSelectors.join()));
	if (prLinks.length === 0) {
		return false;
	}

	const currentRepository = getRepositoryInfo();
	const query = buildQuery(prLinks.map(pr => pr.id));
	const [data, defaultBranch] = await Promise.all([
		api.v4(query),
		getDefaultBranch()
	]);

	for (const prLink of prLinks) {
		if (!data.repository[prLink.id].headOwner) { // 👻 @ghost user
			return;
		}

		let branches;
		let {base, head} = normalizeBranchInfo(data.repository[prLink.id]);

		if (base!.label === defaultBranch) {
			base = undefined;
		}

		if (head!.owner !== currentRepository.owner) {
			head = undefined;
		}

		if (base && head) {
			branches = <>From {createLink(head)} into {createLink(base)}</>;
		} else if (head) {
			branches = <>From {createLink(head)}</>;
		} else if (base) {
			branches = <>To {createLink(base)}</>;
		} else {
			continue;
		}

		prLink.parentElement!.querySelector('.text-small.text-gray')!.append(
			<span className="issue-meta-section d-inline-block">
				<PullRequestIcon/> {branches}
			</span>
		);
	}
}

void features.add({
	id: __filebasename,
	description: 'Shows head and base branches in PR lists if they’re significant: The base branch is added when it’s not the repo’s default branch; The head branch is added when it’s from the same repo or the PR is by the current user.',
	screenshot: 'https://user-images.githubusercontent.com/1402241/51428391-ae9ed500-1c35-11e9-8e54-6b6a424fede4.png'
}, {
	include: [
		pageDetect.isRepoDiscussionList
	],
	init
});