Greasy Fork is available in English.

GitHub Commit Compare

Add controls to compare commits.

// ==UserScript==
// @name             GitHub Commit Compare
// @namespace        https://github.com/jerone/UserScripts
// @description      Add controls to compare commits.
// @author           jerone
// @contributor      darkred
// @copyright        2017+, jerone (https://github.com/jerone)
// @license          CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
// @license          GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @homepage         https://github.com/jerone/UserScripts/tree/master/GitHub_Commit_Compare
// @homepageURL      https://github.com/jerone/UserScripts/tree/master/GitHub_Commit_Compare
// @supportURL       https://github.com/jerone/UserScripts/issues
// @contributionURL  https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
// @icon             https://github.githubassets.com/pinned-octocat.svg
// @include          https://github.com/*/*/commits
// @include          https://github.com/*/*/commits/*
// @exclude          https://github.com/*/*.diff
// @exclude          https://github.com/*/*.patch
// @version          0.0.3
// @grant            none
// ==/UserScript==

// cSpell:ignore tooltipped, Consolas, Menlo, compareAdisabled, compareBdisabled

(function () {
	function addButton() {
		var nav;
		if ((nav = document.querySelector(".file-navigation"))) {
			// Check if our group of buttons are already attached.
			// Remove it, as the 'old' buttons don't have events anymore.
			const e = document.getElementById("GitHubCommitCompareGroup");
			if (e) {
				e.parentElement.removeChild(e);
			}
			Array.from(
				document.querySelectorAll(".GitHubCommitCompareButtonAB"),
			).forEach(function (b) {
				b.parentElement.removeChild(b);
			});

			const c = document.createElement("input");
			c.type = "checkbox";
			c.addEventListener("change", function () {
				const bb = document.querySelectorAll(
					".GitHubCommitCompareButtonAB",
				);
				if (this.checked) {
					if (bb.length === 0) {
						addCompareButtons();
					} else {
						Array.from(bb).forEach(function (b) {
							b.classList.remove("d-none");
						});
					}
				} else {
					Array.from(bb).forEach(function (b) {
						b.classList.add("d-none");
					});
				}
				const bbb = document.getElementById(
					"GitHubCommitCompareButton",
				);
				if (bbb) bbb.classList.remove("disabled");
			});

			const l = document.createElement("label");
			l.classList.add("tooltipped", "tooltipped-n");
			l.setAttribute("aria-label", "Show commit compare buttons");
			l.style.cssText = `
				float: left;
				padding: 3px 10px;
				font-size: 12px;
				font-weight: 600;
				line-height: 20px;
				color: #24292e;
				vertical-align: middle;
				background-color: #fff;
				border: 1px solid rgba(27,31,35,0.2);
				border-right: 0;
				border-top-left-radius: 3px;
				border-bottom-left-radius: 3px;
			`;
			l.appendChild(c);

			const p = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"path",
			);
			p.setAttributeNS(
				null,
				"d",
				"M5 12H4c-.27-.02-.48-.11-.69-.31-.21-.2-.3-.42-.31-.69V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V11c.03.78.34 1.47.94 2.06.6.59 1.28.91 2.06.94h1v2l3-3-3-3v2zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm11 9.48V5c-.03-.78-.34-1.47-.94-2.06-.6-.59-1.28-.91-2.06-.94H9V0L6 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 12 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z",
			);

			const s = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"svg",
			);
			s.classList.add("octicon", "octicon-diff");
			s.setAttributeNS(null, "height", 16);
			s.setAttributeNS(null, "width", 14);
			s.setAttributeNS(null, "viewBox", "0 0 14 16");
			s.appendChild(p);

			const a = document.createElement("a");
			a.id = "GitHubCommitCompareButton";
			a.classList.add(
				"btn",
				"btn-sm",
				"tooltipped",
				"tooltipped-n",
				"disabled",
			);
			a.setAttribute("href", "#");
			a.setAttribute("rel", "nofollow");
			a.setAttribute("aria-label", "Compare these commits");
			a.style.cssText = `
				border-top-left-radius: 0;
				border-bottom-left-radius: 0;
				font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
			`;
			a.appendChild(s);
			a.appendChild(document.createElement("span"));

			const g = document.createElement("div");
			g.id = "GitHubCommitCompareGroup";
			g.classList.add("float-right");
			g.appendChild(l);
			g.appendChild(a);

			nav.appendChild(g);
		}
	}

	function updateRadioButtons() {
		var compareAdisabled = true;
		var compareBdisabled = false;

		const compares = document.querySelectorAll(
			".GitHubCommitCompareButtonAB",
		);
		Array.from(compares).forEach(function (compare) {
			const compareA = compare.querySelector(
				'[name="GitHubCommitCompareButtonA"]',
			);
			const compareB = compare.querySelector(
				'[name="GitHubCommitCompareButtonB"]',
			);

			compareA.disabled = compareAdisabled;
			compareA.parentNode.classList.toggle("disabled", compareAdisabled);

			if (compareA.checked) {
				compareBdisabled = true;
			}
			if (compareB.checked) {
				compareAdisabled = false;
			}

			compareB.disabled = compareBdisabled;
			compareB.parentNode.classList.toggle("disabled", compareBdisabled);
		});

		updateCompareButton();
	}

	function updateCompareButton() {
		const repo = document.querySelector('meta[property="og:url"]').content;

		const compareA = document.querySelector(
			'.GitHubCommitCompareButtonAB [name="GitHubCommitCompareButtonA"]:checked',
		);
		const hashA =
			compareA.parentNode.parentNode.parentNode.querySelector(
				"clipboard-copy",
			).value;
		const compareB = document.querySelector(
			'.GitHubCommitCompareButtonAB [name="GitHubCommitCompareButtonB"]:checked',
		);
		const hashB =
			compareB.parentNode.parentNode.parentNode.querySelector(
				"clipboard-copy",
			).value;

		const a = document.getElementById("GitHubCommitCompareButton");
		a.setAttribute("href", `${repo}/compare/${hashA}...${hashB}`);
		a.querySelector("span").textContent =
			` ${hashA.substring(0, 7)}...${hashB.substring(0, 7)}`;

		//localStorage.setItem('GitHubCommitCompareButtonHashA', hashA);
		//localStorage.setItem('GitHubCommitCompareButtonHashB', hashB);
	}

	function addCompareButtons() {
		const commits = document.querySelectorAll(
			".commits-list-item .commit-links-cell",
		);
		Array.from(commits).forEach(function (item, index) {
			const c1 = document.createElement("input");
			c1.name = "GitHubCommitCompareButtonA";
			c1.type = "radio";
			c1.addEventListener("change", updateRadioButtons);
			if (index === 1) c1.checked = true;

			const l1 = document.createElement("label");
			l1.classList.add(
				"btn",
				"btn-outline",
				"BtnGroup-item",
				"tooltipped",
				"tooltipped-s",
			);
			l1.setAttribute("aria-label", "Choose a base commit");
			l1.appendChild(c1);

			const c2 = document.createElement("input");
			c2.name = "GitHubCommitCompareButtonB";
			c2.type = "radio";
			c2.addEventListener("change", updateRadioButtons);
			if (index === 0) c2.checked = true;

			const l2 = document.createElement("label");
			l2.classList.add(
				"btn",
				"btn-outline",
				"BtnGroup-item",
				"tooltipped",
				"tooltipped-s",
			);
			l2.setAttribute("aria-label", "Choose a head commit");
			l2.appendChild(c2);

			// const p3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
			// p3.setAttributeNS(null,
			//'d',
			//'M5 12H4c-.27-.02-.48-.11-.69-.31-.21-.2-.3-.42-.31-.69V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V11c.03.78.34 1.47.94 2.06.6.59 1.28.91 2.06.94h1v2l3-3-3-3v2zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm11 9.48V5c-.03-.78-.34-1.47-.94-2.06-.6-.59-1.28-.91-2.06-.94H9V0L6 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 12 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z');

			// const s3 = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
			// s3.classList.add('octicon', 'octicon-diff');
			// s3.setAttributeNS(null, 'height', 16);
			// s3.setAttributeNS(null, 'width', 14);
			// s3.setAttributeNS(null, 'viewBox', '0 0 14 16');
			// s3.appendChild(p3);

			// const l3 = document.createElement('a');
			// l3.classList.add('btn', 'btn-outline', 'BtnGroup-item', 'tooltipped', 'tooltipped-sw');
			// l3.setAttribute('aria-label', 'TODO');
			// l3.appendChild(s3);

			const gg = document.createElement("div");
			gg.classList.add(
				"GitHubCommitCompareButtonAB",
				"commit-links-group",
				"BtnGroup",
			);
			gg.appendChild(l1);
			gg.appendChild(l2);
			//gg.appendChild(l3);

			//item.style.width = '350px';
			if (item.querySelector(".muted-link")) {
				// Insert after number of comments button.
				item.insertBefore(
					gg,
					item.querySelector(".muted-link").nextSibling,
				);
			} else {
				item.insertBefore(gg, item.firstChild);
			}
		});

		updateRadioButtons(); // Update radio buttons.
	}

	// Init.
	addButton();

	// Pjax.
	document.addEventListener("pjax:end", addButton);
})();