24

The GitHub file browser lists a file's name and info about the last commit:

GitHub file browser

Is there a way to add each file's size to these listings?

Sphinxxx
  • 12,484
  • 4
  • 54
  • 84

5 Answers5

43

If there is no official way to do this, here is a bookmarklet which uses GitHub's API to add size info (won't work in IE):

javascript:(function(){function f(a){var g=document.querySelector('div[role="rowheader"] a[title="'+a.name+'"]').closest('div[role="row"]').lastElementChild,c=document.createElement("div");c.style.width="5em";"file"===a.type&&(c.textContent=(a.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",c.style.textAlign="right",c.style.whiteSpace="nowrap");g.insertAdjacentElement("beforebegin",c)}var b=window.location.pathname.split("/"),d=b[1],h=b[2],e=b[4];b=b.slice(5);d=["https://api.github.com/repos",d,h,"contents"].concat(b||[]).join("/")+(e?"?ref="+e:"");console.log(d);fetch(d).then(function(a){return a.json()}).then(function(a){return Array.isArray(a)?a.forEach(f):console.warn(a)})})();

File sizes added

Unminified source:

(function () {
    "use strict";

    //Parse the current GitHub repo url. Examples:
    //  Repo root:           /Sphinxxxx/vanilla-picker
    //  Subfolder:           /Sphinxxxx/vanilla-picker/tree/master/src/css
    //  Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
    //  Subfolder at tag:    /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
    //
    //If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
    //Here, we put that in the "ref" variable:
    const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/');

    //Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
    //Example:
    //  https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22
    const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
          url = query.join('/') + (ref ? '?ref=' + ref : '');
    console.log(url);

    //https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
    fetch(url).then(r => r.json())
              .then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn(j));

    function handleFileInfo(info) {
        //console.log(info);
        const link = document.querySelector(`div[role="rowheader"] a[title="${info.name}"]`);
        const timeCol = link.closest('div[role="row"]').lastElementChild;

        const sizeCol = document.createElement('div');
        sizeCol.style.width = '5em';
        if(info.type === 'file') {
            //http://stackoverflow.com/a/17663871/1869660
            //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
            sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
            sizeCol.style.textAlign = 'right';
            sizeCol.style.whiteSpace = 'nowrap';
        }

        timeCol.insertAdjacentElement('beforebegin', sizeCol);
    }
})();

Create a bookmarklet from the first piece of code, or just copy and paste it to your browser's console.

Sphinxxx
  • 12,484
  • 4
  • 54
  • 84
  • Nice. +1. I hope that does not create a burden on the API, I know there is a call rate limit, so if you browse too much... that won't work. – VonC Jul 30 '16 at 15:29
  • @VonC - Good point! Looks like the limit is 60 requests per hour (and no size limit): https://developer.github.com/v3/#rate-limiting – Sphinxxx Jul 31 '16 at 12:38
  • Does it work for private repos. It got a 404 XHR error for me. – Raymond Apr 22 '19 at 04:44
  • 2
    @RaymondPeng - Accessing private repos through the API probably requires authentication, which this bookmarklet doesn't do: https://developer.github.com/v3/#authentication – Sphinxxx Apr 22 '19 at 15:21
  • Thanks! Could you update it to the latest Github layout? It doesn't seem to work anymore. – d33tah Jun 24 '20 at 14:29
  • @d33tah - Updated – Sphinxxx Jun 25 '20 at 13:49
6

For Chrome browsers there exists an extension:

https://chrome.google.com/webstore/detail/github-repository-size/apnjnioapinblneaedefcnopcjepgkci

Sebastian
  • 1,834
  • 2
  • 10
  • 22
1

You can force file sizes to appear with a JavaScript bookmarklet that calls the GitHub API.

GitHub in 2023 now has two kinds of file table (one is a <table>, the other is <div>-based) so I prepared this new bookmarklet that detects which style is in use before modifying it:

javascript:(function e(){let t=[...document.querySelectorAll('div[role="row"]')],n;if(t.length||(t=[...document.querySelectorAll("tbody tr")],n=document.querySelector("thead tr")),(t=t.slice(1)).length&&"github.com"===document.location.host){let[,l,i,r,o,...c]=window.location.pathname.split("/");if(i&&("tree"===r||!r)){let s=["https://api.github.com/repos",l,i,"contents"].concat(c||[]).join("/")+(o?"?ref="+o:"");console.log(s),fetch(s).then(e=>e.json()).then(e=>{let l=new Map;if(e.forEach(e=>l.set(e.name,e)),n&&"Size"!==n.children[1].innerText){let i=document.createElement("th");i.style.width="6em",i.textContent="Size",n.firstElementChild.insertAdjacentElement("afterend",i)}for(let r of t){let o=r.children[n?0:1],c=o.innerText;c.indexOf("\n")>0&&(c=c.slice(0,c.indexOf("\n")));let s=l.get(c),d=document.createElement(n?"td":"div");d.style.width="6em",d.className=o.className,d.innerText=a(s?.size),o.insertAdjacentElement("afterend",d)}})}else console.error("GITHUB PATH NOT UNDERSTOOD")}else console.error("GITHUB TABLE NOT FOUND; can't add file sizes");function a(e){if(!e)return null==e?"–":"0";if(e<0)return"-"+a(-e);let t;return e<1024?e+" B":(e<1048576?(t=" KiB",e/=1024):e<1073741824?(t=" MiB",e/=1048576):(t=" GiB",e/=1073741824),e.toFixed(1)+t)}})();

See instructions here for making a bookmarklet.

example output

Here's the original unminified code:

(function addSizes() {
  // Sometimes each row is like `<div role="row" class="Box-row...">` with no column headers
  //   e.g. https://github.com/qwertie/ecsharp
  // Sometimes each row is like `<tr class="react-directory-row">` with header in `<thead...><tr...>`
  //   e.g. https://github.com/qwertie/ecsharp/tree/master/Main
  // Look for the first kind of rows; if not found, look for the other kind
  let rows = [...document.querySelectorAll(`div[role="row"]`)], header;
  if (!rows.length) {
    rows = [...document.querySelectorAll(`tbody tr`)];
    header = document.querySelector(`thead tr`);
  }
  rows = rows.slice(1);
    
  if (rows.length && document.location.host === 'github.com') {
    // Parse path after https://github.com, e.g.
    //     /username/repo
    //     /username/repo/tree/branch-name
    //     /username/repo/tree/branch-name/folder-name
    let [/* Leading slash */, owner, repo, __tree__, branch, ...path] = window.location.pathname.split('/');
    if (repo && (__tree__ === 'tree' || !__tree__)) {
      let query = ['https://api.github.com/repos', owner, repo, 'contents']
        .concat(path || []).join('/') + (branch ? '?ref=' + branch : '');
      console.log(query);
      // The GitHub API will returns an array of objects like
      //   [{ "name": "file.py", "path": "folder/file.py", "size": 5226, ... }, ...]
      fetch(query).then(r => r.json()).then(files => {
        // Index the results by file name
        let fileMap = new Map();
        files.forEach(file => fileMap.set(file.name, file));

        // If there is a header row, add a header cell for the file size. Note:
        // If user browses to another folder, the 'Size' header column still exists
        // but no file sizes. In that case, avoid adding another 'Size' column.
        if (header && header.children[1].innerText !== 'Size') {
            let sizeCol = document.createElement('th');
            sizeCol.style.width = '6em';
            sizeCol.textContent = 'Size';
            header.firstElementChild.insertAdjacentElement('afterend', sizeCol);
        }

        // For each row of the table: get the file name, look it up in 
        // fileMap, get the file size, and insert a size column.
        for (let row of rows) {
            let nameCol = row.children[header ? 0 : 1];
            let name = nameCol.innerText;
            if (name.indexOf('\n') > 0) name = name.slice(0, name.indexOf('\n'));
            let file = fileMap.get(name);
            let sizeCol = document.createElement(header ? 'td' : 'div');
            sizeCol.style.width = '6em';
            sizeCol.className = nameCol.className;
            sizeCol.innerText = formatFileSize(file?.size);
            nameCol.insertAdjacentElement('afterend', sizeCol);
        }
      });
    } else {
      console.error('GITHUB PATH NOT UNDERSTOOD');
    }
  } else {
    console.error("GITHUB TABLE NOT FOUND; can't add file sizes");
  }
  
  function formatFileSize(size) {
    if (!size) return size == null ? '–' : '0';
    if (size < 0) return '-' + formatFileSize(-size);
    
    let suffix;
    if (size < 1024) {
        return size + ' B';
    } else if (size < 1024*1024) {
        suffix = ' KiB';
        size /= 1024;
    } else if (size < 1024*1024*1024) {
        suffix = ' MiB';
        size /= 1024*1024;
    } else {
        suffix = ' GiB';
        size /= 1024*1024*1024;
    }
    
    return size.toFixed(1) + suffix;
  };
})();

It's based loosely on @Sphinxxx's code, but the file size will be the second column rather than the third, and the units change automatically as the file size increases.

Qwertie
  • 16,354
  • 20
  • 105
  • 148
0

No, the GitHub file browser is not configurable that way.

Getting back that extra information would mean transferring an enormous extra amount of data (for each pages of each repos) for GitHub, so I am not sure this is a feature you would see anytime soon.


Note that 'size' is an acceptable criteria for GitHub search though (meaning that size information is there and can be used, not just for browsing files).

element language:xml size:100

Matches code with the word "element" that's marked as being XML and has exactly 100 bytes.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 4
    Thanks, but I don't see how that would be "an enormous extra amount of data". Could you elaborate a little? – Sphinxxx Jul 31 '16 at 12:35
  • 2
    @Sphinxxx simple: multiply those extra bits by the number of pages served every day by GitHub, and you will get GB of *additional* data to transger (not to mention to compute and to cache). Adding any extra information (like the size) if *not* trivial. If you want to know more about scalability issue at GitHub, listen to http://softwareengineeringdaily.com/2016/07/26/scaling-github-with-sam-lambert/ – VonC Jul 31 '16 at 12:50
  • 7
    I mean, if you want to have a popular web app, you have to transmit information. That's what we're paying for. I dearly hope they have better reasons than "sending file size info across the wire is too expensive". They could have it off by default and we'll see how many people want it by the number that turn it on. It's a regular annoyance for me when I'm exploring repos. – Michael Terry Feb 27 '18 at 19:33
  • well "enormous extra amount of data" is relative. such information could be cached. this feature would be pretty much up to github. – phil294 May 30 '18 at 19:11
  • 2
    @Blauhirn I agree, but unfortunately, at GitHub scale, it is very much *not* relative, and quite enormous. Plus caching means you need to maintain consistency of that cached information, which is not trivial considering the Git repositories hosted by GitHub are themselves... distributed(!) Through DGit and Spoke. (https://githubengineering.com/building-resilience-in-spokes/). So... It is indeed up to GitHub, but better them than me: that is no trivial task. – VonC May 30 '18 at 19:16
0

Updated script/bookmarklet after @Sphinxxx answer, now with comments:

Bookmarklet:

!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[role="rowheader"]  a[title="${t.name}"]`;e=document.querySelector(i).closest('div[role="row"]').lastElementChild,(n=document.createElement("div")).style.width="5em",n.textContent=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",n.style.textAlign="right",n.style.whiteSpace="nowrap",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();

Usage:

Right click, copy link, paste into address bar (or into bookmark link), prepend "javascript:" without quotes, press ENTER

Variant for new github layout with folders tree on the left and file contents on the right:

!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[title="${t.name}"]`;e=document.querySelector(i).closest('tr[class="react-directory-row"]').lastElementChild,(n=document.createElement("td")).style.width="5em",n.innerHTML=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();

Script 1:

(function () {
    "use strict";

    //Parse the current GitHub repo url. Examples:
    //  Repo root:           /Sphinxxxx/vanilla-picker
    //  Subfolder:           /Sphinxxxx/vanilla-picker/tree/master/src/css
    //  Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
    //  Subfolder at tag:    /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
    //
    //If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
    //Here, we put that in the "ref" variable:
    const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.

    //Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
    //Example:
    //  https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
    const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
          url = query.join('/') + (ref ? '?ref=' + ref : '');

    //https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
    fetch(url).then(r => r.json())
              .then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));

    function handleFileInfo(info) {
        var timeCol = null;
        var sizeCol = null;
        var link = "";
        var QR = "";
        if(info.type === 'file') { // skip folders
          const QR = `div[role="rowheader"]  a[title="${info.name}"]`; // select the cell containing the file name
          link = document.querySelector(QR);

          ///// Climb along the html hierarchy until it finds a DIV named "row",
          ///// and get last element (last cell of row), i.e. the date column:
          timeCol = link.closest('div[role="row"]').lastElementChild;

          ///// Create label for file size
          sizeCol = document.createElement('div');
          sizeCol.style.width = '5em';
          //http://stackoverflow.com/a/17663871/1869660
          //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
          sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB'; 
          sizeCol.style.textAlign = 'right';
          sizeCol.style.whiteSpace = 'nowrap';

          ///// Insert new label before last element of row:
          timeCol.insertAdjacentElement('beforebegin', sizeCol);
        } else {
            // skip folders
        }

    }
})();

Script 2:

(function () {
    "use strict";

    //Parse the current GitHub repo url. Examples:
    //  Repo root:           /Sphinxxxx/vanilla-picker
    //  Subfolder:           /Sphinxxxx/vanilla-picker/tree/master/src/css
    //  Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
    //  Subfolder at tag:    /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
    //
    //If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
    //Here, we put that in the "ref" variable:
    const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.

    //Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
    //Example:
    //  https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
    const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
          url = query.join('/') + (ref ? '?ref=' + ref : '');

    //https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
    fetch(url).then(r => r.json())
              .then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));

    function handleFileInfo(info) {
        var timeCol = null;
        var sizeCol = null;
        var link = "";
        var QR = "";
        if(info.type === 'file') { // skip folders
          const QR = `div[title="${info.name}"]`; // select the cell containing the file name
          link = document.querySelector(QR);

          ///// Climb along the html hierarchy until it finds a DIV named "row",
          ///// and get last element (last cell of row), i.e. the date column:
          timeCol = link.closest('tr[class="react-directory-row"]').lastElementChild;

          ///// Create label for file size
          sizeCol = document.createElement('td');
          sizeCol.style.width = '5em';
          //http://stackoverflow.com/a/17663871/1869660
          //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
          sizeCol.innerHTML = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
          //sizeCol.style.textAlign = 'right';
          //sizeCol.style.whiteSpace = 'nowrap';

          ///// Insert new label before last element of row:
          timeCol.insertAdjacentElement('beforebegin', sizeCol);
        } else {
            // skip folders
        }

    }
})();
jumpjack
  • 841
  • 1
  • 11
  • 17