Select commits on GitHub and compare them
La data de
// ==UserScript==
// @name GitHub Commit Compare Picker
// @namespace github-commit-compare-picker
// @version 1.1.0
// @description Select commits on GitHub and compare them
// @match https://github.com/*
// @run-at document-idle
// @grant none
// @license MIT
// ==/UserScript==
console.log("GitHub compare userscript loaded");
(function () {
"use strict";
var CHECKBOX_CLASS = "gh-commit-compare-checkbox";
var BUTTON_ID = "gh-commit-compare-button";
function parsePage() {
var match = location.pathname.match(
/^\/([^/]+)\/([^/]+)\/commits(?:\/(.+?))?\/?$/
);
if (!match) {
return null;
}
return {
owner: match[1],
repo: match[2],
branch: match[3] ? decodeURIComponent(match[3]) : "HEAD"
};
}
function getShaLinks(page) {
var prefix =
"/" +
page.owner +
"/" +
page.repo +
"/commit/";
var links = Array.from(
document.querySelectorAll(
'a[href^="' + prefix + '"]'
)
);
return links.filter(function (link) {
var text = (link.innerText || "").trim();
// We specifically want the visible short SHA link.
return /^[0-9a-f]{7,40}$/i.test(text);
});
}
function getSelected() {
return Array.from(
document.querySelectorAll(
"." + CHECKBOX_CLASS + ":checked"
)
).map(function (box) {
return {
sha: box.getAttribute("data-sha"),
order: Number(box.getAttribute("data-order"))
};
});
}
function updateButton() {
var button = document.getElementById(BUTTON_ID);
var page = parsePage();
if (!button || !page) {
return;
}
var selected = getSelected();
if (selected.length === 0) {
button.innerText = "Diff";
button.disabled = true;
button.style.opacity = "0.5";
} else if (selected.length === 1) {
button.innerText = "Diff to " + page.branch;
button.disabled = false;
button.style.opacity = "1";
} else {
button.innerText = "Diff selected";
button.disabled = false;
button.style.opacity = "1";
}
}
function installCheckboxes() {
var page = parsePage();
if (!page) {
return;
}
var shaLinks = getShaLinks(page);
shaLinks.forEach(function (shaLink, index) {
var sha = (shaLink.innerText || "").trim();
// Already installed?
if (
document.querySelector(
"." +
CHECKBOX_CLASS +
'[data-sha="' +
sha +
'"]'
)
) {
return;
}
var box = document.createElement("input");
box.type = "checkbox";
box.className = CHECKBOX_CLASS;
box.setAttribute("data-sha", sha);
box.setAttribute("data-order", String(index));
box.style.width = "14px";
box.style.height = "14px";
box.style.margin = "0 6px";
box.style.padding = "0";
box.style.flex = "0 0 auto";
box.style.alignSelf = "center";
box.style.verticalAlign = "middle";
box.style.cursor = "pointer";
box.addEventListener("change", function () {
if (getSelected().length > 2) {
box.checked = false;
}
updateButton();
});
/*
* GitHub currently looks approximately like:
*
* SHA link
* <div>copy button</div>
*
* Put checkbox between them.
*/
var next = shaLink.nextElementSibling;
if (
next &&
next.querySelector &&
next.querySelector("svg.octicon-copy")
) {
shaLink.parentNode.insertBefore(box, next);
} else {
shaLink.parentNode.insertBefore(
box,
shaLink.nextSibling
);
}
});
/*
* Recalculate order based on actual DOM position.
* GitHub displays newest first.
*/
var boxes = Array.from(
document.querySelectorAll("." + CHECKBOX_CLASS)
);
boxes.forEach(function (box, index) {
box.setAttribute("data-order", String(index));
});
}
function installButton() {
if (document.getElementById(BUTTON_ID)) {
return;
}
var button = document.createElement("button");
button.id = BUTTON_ID;
button.type = "button";
button.innerText = "Diff";
button.disabled = true;
button.style.position = "fixed";
button.style.right = "28px";
button.style.bottom = "28px";
button.style.zIndex = "999999";
button.style.padding = "8px 16px";
button.style.border =
"1px solid rgba(31, 35, 40, 0.15)";
button.style.borderRadius = "6px";
button.style.background = "#1f883d";
button.style.color = "white";
button.style.fontWeight = "600";
button.style.fontSize = "14px";
button.style.boxShadow =
"0 3px 12px rgba(0,0,0,0.20)";
button.style.cursor = "pointer";
button.style.opacity = "0.5";
button.addEventListener("click", function () {
var page = parsePage();
if (!page) {
return;
}
var selected = getSelected();
if (selected.length === 0) {
return;
}
var oldRef;
var newRef;
if (selected.length === 1) {
oldRef = selected[0].sha;
newRef = page.branch;
} else {
selected.sort(function (a, b) {
return a.order - b.order;
});
// GitHub list: newest first.
newRef = selected[0].sha;
oldRef = selected[1].sha;
}
var url =
"https://github.com/" +
page.owner +
"/" +
page.repo +
"/compare/" +
encodeURIComponent(oldRef) +
"..." +
encodeURIComponent(newRef);
window.location.href = url;
});
document.body.appendChild(button);
updateButton();
}
function init() {
if (!parsePage()) {
return;
}
installButton();
installCheckboxes();
updateButton();
}
/*
* GitHub renders commit rows asynchronously.
*
* Polling here is intentional. It is simple and robust,
* and the work performed each time is tiny.
*/
setInterval(function () {
init();
}, 500);
/*
* Also run immediately in case the page is already ready.
*/
init();
})();