Building a GitHub PR Diff for ChatGPT analysis
Easiest way to summarize a pull request is via automation (by feeding it into an LLM)
4-min read
Convert PRs into useful context with this one simple trick! Head to the bottom to see the actual bookmarklet. Open source here and chrome extension here
As an engineer, I often find myself sifting through GitHub pull requests, pulling branches down and opening .diffs
to review changes. It’s a tedious process to understand the full context and then provide helpful comments. After repeating this for months, why not automate everything with a bookmarklet?
ChatGPT 4o is powerful enough to give me summaries of PRs but selecting all the text on the site isn’t good enough and introduces a lot of clutter. It is much better to provide a diff with appropriate context (like full changed source files) so it can paint a clearer picture.
This also helps when I’ve finalized my own PR and want to provide useful pointers on what’s changed. I feed the diff into the LLM along with the GitHub PR template and it spits out the changes instead of having to manually type it out.
Step 1: The Simple Idea – just open the .diff
GitHub already provides .diff
and .patch
files for PRs — by appending the extension to the URL, you get the all the changes in a raw format.
javascript:window.open(window.location.href + '.diff');
What if I'm not on a PR page? I also needed the bookmarklet to know when I was on a valid pull request page and act accordingly.
Step 2: Handling Edge Cases (Because I Click Everywhere)
I quickly realized that I’m not always on the "Files changed" tab or even on a valid PR page. Sometimes I’d hit the bookmarklet while still on the main repo, the list of PRs, or even issues tabs. I needed to handle these cases gracefully.
So, I added checks for specific pages like /pulls
, /issues
, and /actions
, displaying an alert if I wasn’t on a valid PR page. That made things a lot smoother:
if (/\/(pulls|issues|actions)|github\.com\/[^\/]+\/[^\/]+$/.test(window.location.href)) {
alert('You are not on a valid PR page. Please navigate to a specific PR.');
return;
}
Update: This guard evolved over time while building out the chrome extension to guard against all pages that do not follow the format of —
https://github.com/<repo-owner>/<repo-name>/pull/<number>
Step 3: Adding the "View raw" for Files
Once I had the diff part working, the next natural step was to extend the bookmarklets capabilities by opening each changed file in the pull request. This would be helpful in painting a better picture on how the files related to each other and to create a contextual mind map. The dropdown was just a click away, but why click it manually when I can automate that too?
The script was updated to automatically click the ellipsis buttons for each file, wait for the dropdown to appear, and then click on the "View raw" menu section. Here’s what it ended up looking like:
document.querySelectorAll('.file-header').forEach((fileHeader, index) => {
const kebabButton = fileHeader.querySelector('.octicon-kebab-horizontal');
if (kebabButton) {
setTimeout(() => {
kebabButton.closest('summary').click();
setTimeout(() => {
const viewRawLink = fileHeader.closest('.file').querySelector('a[href*="/raw/"]');
if (viewRawLink) window.open(viewRawLink.href, '_blank');
}, 500);
}, index * 1000);
}
});
Learnt the preferred nomenclature is kebab not ellipsis. Now the bookmarklet could handle both the .diff
file and individual "View raw" links for all files in the "Files changed" tab.
The final code
javascript:(function() {
const prUrl = window.location.href;
// List of invalid URL patterns (main page, /pulls, /issues, /actions)
const invalidPatterns = ['/pulls', '/issues', '/actions', /github\.com\/[^\/]+\/[^\/]+$/];
// Check if the URL matches any invalid patterns
if (invalidPatterns.some(pattern => prUrl.match(pattern))) {
alert('You are not on a valid PR page. Please navigate to a specific PR.');
return;
}
// Part 1: Open the diff file after extracting base PR URL
const normalizedUrl = prUrl.split('/pull/')[0] + prUrl.match(/\/pull\/\d+/)[0]; // Extract base PR URL
const diffUrl = normalizedUrl + '.diff';
window.open(diffUrl, '_blank');
// Part 2: If not on the "Files changed" tab, exit after opening the diff
if (!prUrl.includes('/files')) {
return;
}
// Part 3: If on the "Files changed" tab, open the "View raw" links
const fileHeaders = document.querySelectorAll('.file-header');
function openRawView(fileHeader) {
const kebabButton = fileHeader.querySelector('.octicon-kebab-horizontal');
if (kebabButton) {
// Click the ellipsis button to open the dropdown
kebabButton.closest('summary').click();
// Wait for the dropdown to appear, then click "View raw"
setTimeout(() => {
const viewRawLink = fileHeader.closest('.file').querySelector('a[href*="/raw/"]');
if (viewRawLink) {
window.open(viewRawLink.href, '_blank');
} else {
console.error('Could not find the "View raw" link.');
}
}, 500); // Delay to allow the dropdown to appear
} else {
console.error('Could not find the kebab button.');
}
}
// Loop through each file header, trigger the process for "View raw"
fileHeaders.forEach((fileHeader, index) => {
// Stagger the openings to prevent overload
setTimeout(() => openRawView(fileHeader), index * 1000);
});
})();
And a One-Liner Bookmarklet
javascript:(function(){const prUrl=window.location.href;if(/\/(pulls|issues|actions)|github\.com\/[^\/]+\/[^\/]+$/.test(prUrl)){alert('You are not on a valid PR page. Please navigate to a specific PR.');return;}const diffUrl=prUrl.match(/\/pull\/\d+/)?prUrl.split('/pull/')[0]+prUrl.match(/\/pull\/\d+/)[0]+'.diff':null;if(diffUrl)window.open(diffUrl,'_blank');if(!prUrl.includes('/files'))return;document.querySelectorAll('.file-header').forEach((fileHeader,index)=>{const kebabButton=fileHeader.querySelector('.octicon-kebab-horizontal');if(kebabButton){setTimeout(()=>{kebabButton.closest('summary').click();setTimeout(()=>{const viewRawLink=fileHeader.closest('.file').querySelector('a[href*="/raw/"]');if(viewRawLink)window.open(viewRawLink.href,'_blank');},500);},index*1000);}});})();
You can add this by either
using this website or
bookmarking this site, changing the name to GH - ChatGPT or any equivalent, editing the bookmark and pasting the one-liner above in the URL section
Remember to always allow pop-ups when using this for the first time and try not use this on PRs with many files changed, it’ll open a bunch of tabs.
Another one - Opening a ChatGPT chat programmatically
If you don’t want to copy the contents of the diff into ChatGPT you can run this bookmarklet which opens a new chat. You do need an account —
Code
(function() {
const prUrl = window.location.href;
// Check if the URL contains ".diff" to ensure it's a valid diff page
if (!prUrl.includes('.diff')) {
alert('This bookmarklet only works on .diff pages.');
return;
}
// Get the contents of the diff file
const diffContent = document.body.innerText;
// Create the prompt for ChatGPT, broken into multiple lines for readability
const prompt = encodeURIComponent(
`You are an expert programmer in React Native and Python. I've provided a diff file.\n\n` +
`I want you to summarize the changes and provide any helpful comments, feedback, fixes, and optimizations that can be done to the code to make it better in bullet points.\n\n` +
`I can also provide the files if needed, let me know.\n\n` +
`Here is the diff:\n\n${diffContent}`
);
// Open ChatGPT with the pre-filled prompt
window.open(`https://chatgpt.com/?q=${prompt}`, '_blank');
})();
And a One-Liner Bookmarklet
javascript:(function(){if(!window.location.href.includes('.diff')){alert('This bookmarklet only works on .diff pages.');return;}const prompt=encodeURIComponent(`You are an expert programmer in React Native and Python. I've provided a diff file. I want you to summarize the changes and provide any helpful comments, feedback, fixes, and optimizations that can be done to the code to make it better in bullet points. I can also provide the files if needed, let me know.\n\n${document.body.innerText}`);window.open(`https://chatgpt.com/?q=${prompt}`,'_blank');})();
Same instructions as above to create a bookmarklet. Happy Friday!
Chrome Extension
Adding bookmarklets is tough. I created a chrome extension that streamlines the workflow for interacting with GitHub pull requests. This tool allows one to quickly open diff files, access raw files, and even send pull request diffs to ChatGPT for feedback—all with the click of a button! The extension is built with TypeScript for type safety and leverages Chrome's powerful scripting and activeTab permissions to interact directly with the GitHub page.
The best part? The tool is fully customizable! I added an editable prompt feature for ChatGPT, so one can fine-tune the kinds of feedback they’re seeking. From improving code quality to reviewing changes more efficiently, this extension has been a game changer in my development workflow. If you prefer a GUI, there is an open source chrome extension available here —
https://github.com/VirenMohindra/github-diff, and here
https://chromewebstore.google.com/detail/github-diff/hhopppbhdogliepfdaihcoicedlieibn