Last active
April 8, 2017 16:30
-
-
Save vsemozhetbyt/25698ab638b51d07811093d4be249991 to your computer and use it in GitHub Desktop.
Check commit messages in PRs for Node.js repository
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript: { | |
| const validPathname = /^\/nodejs\/node\/pull\/\d+(?:\/commits)?$/; | |
| const TITLE_MAX_LENGTH = 50; | |
| const LINE_MAX_LENGTH = 72; | |
| const titleFormat = /^([\d_a-z ,-]+)\b: ([a-z'-]+\b)?/; | |
| const lineWithURLFormat = /^(Fixes|Refs?): (https?:)?/; | |
| const LOG_DELIMITER_LENGTH = 20; | |
| const loaderBar = document.querySelector('#js-pjax-loader-bar'); | |
| const observer = new MutationObserver((mutations) => { | |
| mutations.forEach((mutation) => { | |
| if (mutation.oldValue.includes('is-loading') && | |
| validPathname.test(location.pathname)) check(); | |
| }); | |
| }); | |
| observer.observe(loaderBar, | |
| { attributes: true, attributeOldValue: true, attributeFilter: ['class'] } | |
| ); | |
| check(); | |
| function check() { | |
| let OK = 0; | |
| let notOK = 0; | |
| const allErrors = []; | |
| const commitLinks = [...document.body.querySelectorAll('a[href].message')]; | |
| const hrefSeen = []; | |
| commitLinks.forEach((commitLink, commitNumber) => { | |
| const href = commitLink.href; | |
| if (hrefSeen.includes(href)) return; | |
| hrefSeen.unshift(href); | |
| const commitErrors = []; | |
| const lines = commitLink.title.split('\n'); | |
| const title = lines.shift(); | |
| const [, titlePrefix, lcVerb] = title.match(titleFormat) || []; | |
| const titleLength = [...title].length; | |
| if (!titlePrefix) { | |
| commitErrors.push('Begin commit title with subsystem prefix'); | |
| } | |
| if (!lcVerb) { | |
| commitErrors.push('Use lower case imperative verb after subsystem prefix'); | |
| } | |
| if (titleLength > TITLE_MAX_LENGTH) { | |
| commitErrors.push( | |
| `Keep commit title length within ${ | |
| TITLE_MAX_LENGTH} characters (currently ${titleLength})` | |
| ); | |
| } | |
| if (lines.length && lines[0] !== '') { | |
| commitErrors.push('Insert empty line after commit title'); | |
| } | |
| let lineNumber = 1; | |
| lines.forEach((line) => { | |
| lineNumber++; | |
| const lineLength = [...line].length; | |
| if (lineLength > LINE_MAX_LENGTH) { | |
| commitErrors.push( | |
| `Keep line length within ${ | |
| LINE_MAX_LENGTH} characters: line ${ | |
| lineNumber} length is ${lineLength}:\n${line}` | |
| ); | |
| } | |
| const [, linePrefix, protocol] = line.match(lineWithURLFormat) || []; | |
| if (linePrefix && !protocol) { | |
| commitErrors.push( | |
| `Use full URL with 'Fixes:' and 'Refs:': line ${lineNumber}:\n${line}` | |
| ); | |
| } | |
| }); | |
| if (!commitErrors.length) { | |
| OK++; | |
| commitLink.style.backgroundColor = 'LightGreen'; | |
| } else { | |
| notOK++; | |
| commitLink.style.backgroundColor = 'LightCoral'; | |
| const errorMessage = commitErrors.map(error => `* ${error}`).join('\n\n'); | |
| allErrors.push(`Commit ${commitNumber + 1} (${title}):\n\n${errorMessage}`); | |
| const errorInfo = getOrCreate( | |
| 'span', '.node-check-commit-messages-error', commitLink.parentNode, commitLink.nextSibling | |
| ); | |
| errorInfo.title = errorMessage; | |
| errorInfo.textContent = '!'; | |
| errorInfo.setAttribute('style', | |
| 'padding: 0px 3px 0px 3px; margin: 0px 3px 0px 3px; background-color: LightCoral;' | |
| ); | |
| } | |
| }); | |
| const results = commitLinks.length ? | |
| `OK: ${OK}${notOK ? `\nNot OK: ${notOK}` : ''}` | |
| : | |
| 'No commits found.'; | |
| const parent = document.querySelector('#discussion_bucket, #commits_bucket'); | |
| const resultsInfo = getOrCreate('pre', '#node-check-commit-messages-results', parent); | |
| resultsInfo.textContent = results; | |
| resultsInfo.setAttribute('style', | |
| `position: fixed; top: 0px; left: 0px; z-index: 10000; background-color: ${ | |
| notOK ? 'LightCoral' : OK ? 'LightGreen' : 'LightGray' | |
| }; padding: 3px;` | |
| ); | |
| if (notOK) { | |
| resultsInfo.style.cursor = 'pointer'; | |
| resultsInfo.title = 'click to copy full log'; | |
| const copyImg = getOrCreate( | |
| 'img', '#node-check-commit-messages-results-copy-img', resultsInfo | |
| ); | |
| copyImg.alt = allErrors.join(`\n\n${'-'.repeat(LOG_DELIMITER_LENGTH)}\n\n`); | |
| copyImg.src = | |
| 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; | |
| resultsInfo.addEventListener('click', copyLog); | |
| } | |
| } | |
| function getOrCreate(tag, attr, parent = document.body, nodeToInsertBefore = null) { | |
| let elem = parent.querySelector(`${tag}${attr}`); | |
| if (!elem) { | |
| elem = document.createElement(tag); | |
| elem[attr.startsWith('#') ? 'id' : 'className'] = attr.slice(1); | |
| parent.insertBefore(elem, nodeToInsertBefore); | |
| } | |
| return elem; | |
| } | |
| function copyLog(event) { | |
| const sel = window.getSelection(); | |
| sel.removeAllRanges(); | |
| const range = document.createRange(); | |
| range.selectNode(event.target.querySelector('img')); | |
| sel.addRange(range); | |
| document.execCommand('copy'); | |
| sel.removeAllRanges(); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tampermonkey header example: