GitHub First Commit

Add a link to a GitHub repo's first commit

ของเมื่อวันที่ 30-11-2020 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          GitHub First Commit
// @description   Add a link to a GitHub repo's first commit
// @author        chocolateboy
// @copyright     chocolateboy
// @version       2.7.0
// @namespace     https://github.com/chocolateboy/userscripts
// @license       GPL: https://www.gnu.org/copyleft/gpl.html
// @include       https://github.com/
// @include       https://github.com/*
// @require       https://cdn.jsdelivr.net/npm/[email protected]/dist/cash.min.js
// @grant         GM_log
// @inject-into   auto
// ==/UserScript==

const COMMIT_BAR = 'div.js-details-container[data-issue-and-pr-hovercards-enabled] > *:last-child ul'
const FIRST_COMMIT_LABEL = '<span aria-label="First commit"><strong>1st</strong> commit</span>'

// this function extracts the URL of the repo's first commit and navigates to it.
// it is based on code by several developers, a list of whom can be found here:
// https://github.com/FarhadG/init#contributors
//
// XXX it doesn't work on private repos. a way to do that can be found here,
// but it requires an authentication token:
// https://gist.github.com/simonewebdesign/a70f6c89ffd71e6ba4f7dcf7cc74ccf8
function openFirstCommit (user, repo) {
    return fetch(`https://api.github.com/repos/${user}/${repo}/commits`)
        // the `Link` header has additional URLs for paging.
        // parse the original JSON for the case where no other pages exist
        .then(res => Promise.all([res.headers.get('link'), res.json()]))

        .then(([link, commits]) => {
            if (link) {
                // the link header contains two URLs and has the following
                // format (wrapped for readability):
                //
                //     <https://api.github.com/repositories/1234/commits?page=2>;
                //     rel="next",
                //     <https://api.github.com/repositories/1234/commits?page=9>;
                //     rel="last"

                // extract the URL of the last page (commits are ordered in
                // reverse chronological order, like the git CLI, so the oldest
                // commit is on the last page)
                const lastPage = link.match(/^.+?<([^>]+)>;/)[1]

                // fetch the last page of results
                return fetch(lastPage).then(res => res.json())
            }

            // if there's no link, we know we're on the only page
            return commits
        })

        // get the last commit and navigate to its target URL
        .then(commits => {
            location.href = commits[commits.length - 1].html_url
        })
}

// add the "First commit" link as the last child of the commit bar
//
// XXX on most sites, hitting the back button takes you back to a snapshot of
// the previous DOM tree, e.g. navigating away from a page on Hacker News
// highlighted via Hacker News Highlighter [1], then back to the highlighted
// page, retains the DOM changes (the highlighting). that isn't the case on
// GitHub, which triggers network requests and fragment/page reloads when
// navigating (back) to any (SPA) page, even when not logged in. this means, for
// example, we don't need to check if the first-commit widget already exists (it
// never does), and don't need to restore its old label when navigating away
// from a page (since the widget will always be created from scratch rather than
// reused)
//
// this has not always been the case, and may not be the case in the future (and
// may not be the case in some scenarios I haven't tested, e.g. on mobile), so
// rather than taking it for granted, we assume the site behaves like every
// other site in this respect and code to that (defensive coding). this costs
// nothing (apart from this explanation) and saves us having to scramble for a
// fix if the implementation changes.
//
// [1] https://greatest.deepsurf.us/en/scripts/39311-hacker-news-highlighter
function run () {
    const $commitBar = $(COMMIT_BAR)

    // bail if it's not a repo page
    if (!$commitBar.length) {
        return
    }

    // bail if the widget already exists
    let $firstCommit = $commitBar.find('#first-commit')

    if ($firstCommit.length) {
        return
    }

    /*
     * This is the first LI in the commit bar (UL), which we clone to create the
     * "First commit" widget.
     *
     *   <li class="ml-3">
     *     <a data-pjax="" href="/foo/bar/commits/master" class="link-gray-dark no-underline">
     *       <svg height="16">...</svg>
     *
     *       <span class="d-none d-sm-inline">
     *           <strong>42</strong>
     *           <span aria-label="Commits on master">commits</span>
     *       </span>
     *     </a>
     *   </li>
     */

    // create it
    $firstCommit = $commitBar
        .find('li')
        .eq(0)
        .clone()
        .attr('id', 'first-commit')

    const $link = $firstCommit
        .find('a')
        .removeAttr('href')
        .css('cursor', 'pointer')

    const $label = $(FIRST_COMMIT_LABEL)

    $link.find(':scope > span').empty().append($label)

    const [user, repo] = $('meta[name="octolytics-dimension-repository_network_root_nwo"]')
        .attr('content')
        .split('/')

    // before navigating away from the page, restore the original label. this
    // ensures it has the correct value if we navigate back to the repo page
    // without making a new request (e.g. via the back button)
    const oldLabelHtml = $label.html()

    $(window).on('unload', () => {
        $label.html(oldLabelHtml)
    })

    $link.on('click', () => {
        $label.text('Loading...')
        openFirstCommit(user, repo)
        return false // stop processing the click
    })

    $commitBar.append($firstCommit)
}

$(document).on('pjax:end', run) // run on pjax page loads
$(run) // run on full page loads