GitHub First Commit

Add a link to a GitHub repo's first commit

As of 2024-09-15. See the latest version.

  1. // ==UserScript==
  2. // @name GitHub First Commit
  3. // @description Add a link to a GitHub repo's first commit
  4. // @author chocolateboy
  5. // @copyright chocolateboy
  6. // @version 4.0.2
  7. // @namespace https://github.com/chocolateboy/userscripts
  8. // @license GPL
  9. // @include https://github.com/
  10. // @include https://github.com/*
  11. // @grant GM_log
  12. // @noframes
  13. // ==/UserScript==
  14.  
  15. // NOTE This file is generated from src/github-first-commit.user.ts and should not be edited directly.
  16.  
  17. "use strict";
  18. (() => {
  19. // src/lib/observer.ts
  20. var DUMMY_MUTATIONS = [];
  21. var INIT = { childList: true, subtree: true };
  22. var observe = (target, ...args) => {
  23. const [init, callback] = args.length === 1 ? [INIT, args[0]] : args;
  24. const $callback = (mutations, observer2) => {
  25. observer2.disconnect();
  26. const result = callback(mutations, observer2);
  27. if (!result) {
  28. observer2.observe(target, init);
  29. }
  30. };
  31. const observer = new MutationObserver($callback);
  32. queueMicrotask(() => $callback(DUMMY_MUTATIONS, observer));
  33. return observer;
  34. };
  35.  
  36. // src/github-first-commit.user.ts
  37. // @license GPL
  38. var ID = "first-commit";
  39. var PATH = 'meta[name="analytics-location"][content]';
  40. var USER_REPO = 'meta[name="octolytics-dimension-repository_network_root_nwo"][content]';
  41. var $ = document;
  42. var openFirstCommit = (user, repo) => {
  43. return fetch(`https://api.github.com/repos/${user}/${repo}/commits`).then((res) => Promise.all([res.headers.get("link"), res.json()])).then(([link, commits]) => {
  44. if (!link) {
  45. return commits;
  46. }
  47. const lastPage = link.match(/^.+?<([^>]+)>;/)[1];
  48. return fetch(lastPage).then((res) => res.json());
  49. }).then((commits) => {
  50. if (Array.isArray(commits)) {
  51. location.href = commits.at(-1).html_url;
  52. } else {
  53. console.error(commits);
  54. }
  55. });
  56. };
  57. observe($.body, () => {
  58. const path = $.querySelector(PATH)?.content;
  59. const isRepoPage = path === "/<user-name>/<repo-name>";
  60. if (!isRepoPage) {
  61. return;
  62. }
  63. if ($.getElementById(ID)) {
  64. return;
  65. }
  66. const commitHistory = $.querySelector("div svg.octicon-history")?.closest("div");
  67. if (!commitHistory) {
  68. return;
  69. }
  70. const firstCommit = commitHistory.cloneNode(true);
  71. const label = firstCommit.querySelector(':scope [data-component="text"] > *');
  72. const header = firstCommit.querySelector(":scope h2");
  73. const link = firstCommit.querySelector(":scope a[href]");
  74. const [user, repo] = $.querySelector(USER_REPO).getAttribute("content").split("/");
  75. firstCommit.id = ID;
  76. header.textContent = label.textContent = "1st Commit";
  77. link.removeAttribute("href");
  78. link.setAttribute("aria-label", "First commit");
  79. firstCommit.addEventListener("click", (e) => {
  80. e.preventDefault();
  81. e.stopPropagation();
  82. label.textContent = "Loading...";
  83. openFirstCommit(user, repo);
  84. }, { once: true });
  85. commitHistory.after(firstCommit);
  86. });
  87. })();