GitHub First Commit

Add a link to a GitHub repo's first commit

Fra 23.12.2020. Se den seneste versjonen.

  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 2.7.1
  7. // @namespace https://github.com/chocolateboy/userscripts
  8. // @license GPL: https://www.gnu.org/copyleft/gpl.html
  9. // @include https://github.com/
  10. // @include https://github.com/*
  11. // @require https://cdn.jsdelivr.net/npm/cash-dom@8.1.0/dist/cash.min.js
  12. // @grant GM_log
  13. // @inject-into auto
  14. // ==/UserScript==
  15.  
  16. const COMMIT_BAR = 'div.js-details-container[data-issue-and-pr-hovercards-enabled] > *:last-child ul'
  17. const FIRST_COMMIT_LABEL = '<span aria-label="First commit"><strong>1st</strong> commit</span>'
  18.  
  19. /*
  20. * this function extracts the URL of the repo's first commit and navigates to it.
  21. * it is based on code by several developers, a list of whom can be found here:
  22. * https://github.com/FarhadG/init#contributors
  23. *
  24. * XXX it doesn't work on private repos. a way to do that can be found here,
  25. * but it requires an authentication token:
  26. * https://gist.github.com/simonewebdesign/a70f6c89ffd71e6ba4f7dcf7cc74ccf8
  27. */
  28. function openFirstCommit (user, repo) {
  29. return fetch(`https://api.github.com/repos/${user}/${repo}/commits`)
  30. // the `Link` header has additional URLs for paging.
  31. // parse the original JSON for the case where no other pages exist
  32. .then(res => Promise.all([res.headers.get('link'), res.json()]))
  33.  
  34. .then(([link, commits]) => {
  35. if (link) {
  36. // the link header contains two URLs and has the following
  37. // format (wrapped for readability):
  38. //
  39. // <https://api.github.com/repositories/1234/commits?page=2>;
  40. // rel="next",
  41. // <https://api.github.com/repositories/1234/commits?page=9>;
  42. // rel="last"
  43.  
  44. // extract the URL of the last page (commits are ordered in
  45. // reverse chronological order, like the git CLI, so the oldest
  46. // commit is on the last page)
  47.  
  48. // @ts-ignore
  49. const lastPage = link.match(/^.+?<([^>]+)>;/)[1]
  50.  
  51. // fetch the last page of results
  52. return fetch(lastPage).then(res => res.json())
  53. }
  54.  
  55. // if there's no link, we know we're on the only page
  56. return commits
  57. })
  58.  
  59. // get the last commit and navigate to its target URL
  60. .then(commits => {
  61. location.href = commits[commits.length - 1].html_url
  62. })
  63. }
  64.  
  65. /*
  66. * add the "First commit" link as the last child of the commit bar
  67. */
  68. function run () {
  69. const $commitBar = $(COMMIT_BAR)
  70.  
  71. // bail if it's not a repo page
  72. if (!$commitBar.length) {
  73. return
  74. }
  75.  
  76. // delete (i.e. replace) the (possibly inert/unresponsive) widget if it
  77. // already exists
  78. $commitBar.find('#first-commit').remove()
  79.  
  80. /*
  81. * This is the first LI in the commit bar (UL), which we clone to create the
  82. * "First commit" widget.
  83. *
  84. * <li class="ml-3">
  85. * <a data-pjax="" href="/foo/bar/commits/master" class="link-gray-dark no-underline">
  86. * <svg height="16">...</svg>
  87. *
  88. * <span class="d-none d-sm-inline">
  89. * <strong>42</strong>
  90. * <span aria-label="Commits on master">commits</span>
  91. * </span>
  92. * </a>
  93. * </li>
  94. */
  95.  
  96. // create it
  97. const $firstCommit = $commitBar
  98. .find('li')
  99. .eq(0)
  100. .clone()
  101. .attr('id', 'first-commit')
  102.  
  103. const $link = $firstCommit
  104. .find('a')
  105. .removeAttr('href')
  106. .css('cursor', 'pointer')
  107.  
  108. const $label = $(FIRST_COMMIT_LABEL)
  109.  
  110. $link.find(':scope > span').empty().append($label)
  111.  
  112. // @ts-ignore
  113. const [user, repo] = $('meta[name="octolytics-dimension-repository_network_root_nwo"]')
  114. .attr('content')
  115. .split('/')
  116.  
  117. $link.on('click', () => {
  118. $label.text('Loading...')
  119. openFirstCommit(user, repo)
  120. return false // stop processing the click
  121. })
  122.  
  123. $commitBar.append($firstCommit)
  124. }
  125.  
  126. $(document).on('pjax:end', run) // run on pjax page loads
  127. $(run) // run on full page loads