- // ==UserScript==
- // @name Github PR Incremental Diffs
- // @version 1.0
- // @namespace https://tampermonkey.net/
- // @homepage https://github.com/sociomantic-tsunami/kelpie
- // @supportURL https://github.com/sociomantic-tsunami/kelpie/issues
- // @description Provides you incremental diffs with the help of an extra server
- // @author Mathias L. Baumann
- // @match *://github.com/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_addStyle
- // @grant GM_getResourceText
- // @grant GM_xmlhttpRequest
- // @require https://cdn.rawgit.com/cemerick/jsdifflib/da7da27640a30537cea9069622dc71600c5a1d61/difflib.js
- // @require https://cdn.rawgit.com/cemerick/jsdifflib/da7da27640a30537cea9069622dc71600c5a1d61/diffview.js
- // @resource CSSDIFF https://cdn.rawgit.com/cemerick/jsdifflib/da7da27640a30537cea9069622dc71600c5a1d61/diffview.css
- // ==/UserScript==
-
- class FileTree
- {
- /* Params:
- sha = sha of the file tree
- url = api url of the file tree
- */
- constructor ( sha, url, root_path )
- {
- this.root_path = root_path;
- this.sha = sha;
- this.url = url;
- this.list = [];
- }
-
- // Fetches the tree from using the API and calls callback with the result
- fetch ( cbthis, callback )
- {
- if (this.list && this.list.length > 0)
- {
- callback(this);
- return;
- }
-
- var request = new XMLHttpRequest();
-
- var receiveTree = function ( )
- {
- var response = JSON.parse(this.responseText);
-
- for (var i=0; i < response.tree.length; i++)
- {
- var obj = { "path" : this.outside.root_path + response.tree[i].path,
- "sha" : response.tree[i].sha,
- "url" : response.tree[i].url,
- "type" : response.tree[i].type };
-
- // Don't get the blob for tree's, get it as another tree
- if (response.tree[i].type == "tree")
- obj.url = obj.url.replace(/blobs/, "trees");
-
- this.outside.list.push(obj);
- //console.log("entry info " + obj.path + ", " + obj.sha);
- }
-
- this.userCb.call(this.cbthis, this.outside);
- };
-
- request.outside = this;
- request.onload = receiveTree;
- request.userCb = callback;
- request.cbthis = cbthis;
-
- // Initialize a request
- request.open('get', this.url);
-
- var usertoken = GM_getValue("username") + ":" + GM_getValue("token");
- request.setRequestHeader("Authorization", "Basic " + btoa(usertoken));
- // Send it
- request.send();
- }
- }
-
- class FileDiffer
- {
- constructor ( base, head, original )
- {
- // List of files that have changed
- this.changed = [];
-
- this.base = base;
- this.head = head;
- this.original = original;
- }
-
- fetch ( cbthis, callback )
- {
- this.cbthis = cbthis;
- this.callback = callback;
-
- if (this.base === null)
- this.base = { "list" : [] };
- else
- {
- var tmp_base = this.base;
- this.base = null;
- tmp_base.fetch(this, this.assignBase);
- }
-
- if (this.head === null)
- this.head = { "list" : [] };
- else
- {
- var tmp_head = this.head;
- this.head = null;
- tmp_head.fetch(this, this.assignHead);
- }
-
- if (this.original === null)
- this.original = { "list" : [] };
- else
- {
- var tmp_orig = this.original;
- this.original = null;
- tmp_orig.fetch(this, this.assignOriginal);
- }
- }
-
- assignBase ( base ) { this.base = base; this.checkComplete(); }
- assignHead ( head ) { this.head = head; this.checkComplete(); }
- assignOriginal ( original ) { this.original = original; this.checkComplete(); }
-
- checkComplete ( )
- {
- if (!this.base || !this.head || !this.original)
- return;
-
- console.log("Received all trees, extracting required files");
-
- var diff = null;
- var i = 0;
- var head_el = null;
-
- var matchPath = function (el) { return el.path==head_el.path; };
-
- // Find all paths differing from base
- for (i=0; i < this.head.list.length; i++)
- {
- head_el = this.head.list[i];
-
- var orig_path = this.original.list.find(matchPath);
-
- // If this path exists in original with the same sha, it was added through a rebase
- if (orig_path !== undefined && orig_path.sha == head_el.sha)
- continue; // so ignore it
-
- var base_path = this.base.list.find(matchPath);
-
- if (orig_path === undefined)
- orig_path = null;
-
- // base doesn't have that file?
- if (base_path === undefined)
- { // completely new file
- diff = { "base" : null, "head" : head_el,
- "orig" : null };
- console.log("File differs (no base): " + head_el.path);
- this.changed.push(diff);
- continue;
- }
-
- // file exists in base and differs
- if (base_path.sha != head_el.sha)
- { // changes have been made
- diff = { "base" : base_path, "head" : head_el,
- "orig" : orig_path };
-
- console.log("File differs: " + head_el.path);
- this.changed.push(diff);
- continue;
- }
- }
-
- // Find any files not existing in head, but existing in base
- for (i=0; i < this.base.list.length; i++)
- {
- var base_el = this.base.list[i];
-
- head_el = this.head.list.find(matchPath);
-
- if (head_el !== undefined)
- continue;
-
- diff = { "base" : base_el, "head" : null, "orig" : null };
- console.log("File differs (no head): " + base_el.path);
- this.changed.push(diff);
- }
-
- this.recurseTree();
- }
-
- // recurses into tree objects in our "changed" paths list and looks for diffs
- recurseTree ( )
- {
- var i = 0;
- var el = {};
- var base_tree = {};
- var head_tree = {};
- var orig_tree = {};
- var did_recurse = false;
- var path = "";
-
- console.log("Recursing...");
-
- // Prepare to recurse
- for (i=0; i < this.changed.length; i++)
- {
- el = this.changed[i];
-
- // No need to recurse if one is null
- if (el.head === null || el.base === null)
- continue;
-
- // we can only recurse into trees
- if (el.head.type != "tree" && el.base.type != "tree")
- continue;
-
- if (el.base.type == "tree")
- base_tree = new FileTree(el.base.sha, el.base.url, el.base.path + "/");
- else
- base_tree = null;
-
- if (el.head.type == "tree")
- head_tree = new FileTree(el.head.sha, el.head.url, el.head.path + "/");
- else
- head_tree = null;
-
- if (el.orig !== null && el.orig.type == "tree")
- orig_tree = new FileTree(el.orig.sha, el.orig.url, el.orig.path + "/");
- else
- orig_tree = null;
-
- console.log("Recurse task " + el.head.path);
-
- el.pending = new FileDiffer(base_tree, head_tree, orig_tree);
- }
-
- // Actually do the recursion
- for (i=0; i < this.changed.length; i++)
- {
- el = this.changed[i];
-
- if ("pending" in el)
- {
- console.log("Starting task.. " + el.base.path);
- el.pending.fetch(this, this.recurseCallback);
- did_recurse = true;
- }
- }
-
- if (did_recurse === false)
- this.fetchAllFiles();
- }
-
- // called once for every recursion
- // * merges the recursed tree with ours
- // * if no more callbacks pending, calls user cb
- recurseCallback ( file_differ )
- {
- var still_waiting = false;
-
- for (var i=0; i < this.changed.length; i++)
- {
- var el = this.changed[i];
-
- if ("pending" in el && el.pending == file_differ)
- {
- console.log("recurseCb for " + el.base.path + " updated");
- this.changed = this.changed.concat(file_differ.changed);
- el.pending = null;
- continue;
- }
-
- if ("pending" in el && el.pending !== null)
- {
- console.log("Still waiting for " + el.base.path);
- still_waiting = true;
- }
- }
-
- if (still_waiting)
- {
- return;
- }
-
- this.fetchAllFiles();
- }
-
- fetchAllFiles ( )
- {
- console.log("Fetching files...");
- for (var i=0; i < this.changed.length; i++)
- {
- var el = this.changed[i];
-
- if (el.base && el.base.url && !("content" in el.base))
- this.fetchFile(el.base.url);
-
- if (el.head && el.head.url && !("content" in el.head))
- this.fetchFile(el.head.url);
- }
-
- if (this.changed.length === 0)
- this.checkReceivedFiles();
- }
-
- fetchFile ( url )
- {
- console.log("Fetching " + url);
- var request = new XMLHttpRequest();
-
- var receiveBlob = function ( )
- {
- var response = JSON.parse(this.responseText);
-
- function findMatch (elem)
- {
- if (elem.base && elem.base.sha == response.sha)
- return true;
- else if (elem.head && elem.head.sha == response.sha)
- return true;
-
- return false;
- }
-
- var el = this.outside.changed.find(findMatch);
-
- if (el === undefined)
- {
- console.log("received unexpected sha " + response.sha);
- return;
- }
-
- console.log("Received content for " + el.head.path);
-
- var content = "content" in response && response.content.length > 0 ? atob(response.content) : "";
-
- if (el.base.sha == response.sha)
- el.base.content = content;
- else if (el.head.sha == response.sha)
- el.head.content = content;
- else
- console.log("Unmatched sha?!");
-
- this.outside.checkReceivedFiles();
- };
-
- request.outside = this;
- request.onload = receiveBlob;
-
- // Initialize a request
- request.open('get', url);
-
- var usertoken = GM_getValue("username") + ":" + GM_getValue("token");
- request.setRequestHeader("Authorization", "Basic " + btoa(usertoken));
- // Send it
- request.send();
- }
-
- checkReceivedFiles ( )
- {
- var all_content_received = true;
-
- for (var i=0; i < this.changed.length; i++)
- {
- var el = this.changed[i];
-
- if (el.base && !("content" in el.base) ||
- el.head && !("content" in el.head))
- {
- all_content_received = false;
- break;
- }
- }
-
- if (all_content_received)
- {
- console.log("Received all content, calling cb");
- this.callback.call(this.cbthis, this);
- }
- }
- }
-
-
- class Fetcher
- {
- constructor ( )
- {
- this.files = [];
- }
-
- start ( owner, repo, pr, commit1, commit2, element )
- {
- this.sha_base = commit1;
- this.sha_head = commit2;
- this.owner = owner;
- this.repo = repo;
- this.element = element;
- this.base_tree = null;
- this.head_tree = null;
- this.orig_tree = null;
-
- this.usertoken = GM_getValue("username") + ":" + GM_getValue("token");
-
- //this.fetchCommit(this.sha_update, "update");
- this.fetchPrBase(pr);
- }
-
- // Fetches the base branch for the PR and extracts the latest commits sha
- fetchPrBase ( pr )
- {
- console.log("Fetching PR base");
- var receivePr = function ( )
- {
- var response = JSON.parse(this.responseText);
-
- this.outside.fetchTreeShas(this.outside.sha_base, this.outside.sha_head, response.base.sha);
- };
-
- var request = new XMLHttpRequest();
-
- request.outside = this;
- request.onload = receivePr;
- // Initialize a request
- request.open('get', "https://api.github.com/repos/"+this.owner+"/"+this.repo+"/pulls/" + pr);
-
- request.setRequestHeader("Authorization", "Basic " + btoa(this.usertoken));
- // Send it
- request.send();
- }
-
- // Extracts the tree shas from base/head/orig commit
- fetchTreeShas ( base, head, orig )
- {
- console.log("Fetching trees");
- this.fetchTreeFromCommit(base, "base_tree", this.checkTreesDone);
- this.fetchTreeFromCommit(head, "head_tree", this.checkTreesDone);
- this.fetchTreeFromCommit(orig, "orig_tree", this.checkTreesDone);
- }
-
- checkTreesDone ( )
- {
- console.log("checkTreesDone()");
-
- if (!this.base_tree || !this.head_tree || !this.orig_tree)
- {
- console.log("Not all done: " + this.base_tree + " " + this.head_tree + " " + this.orig_tree);
- return;
- }
-
- console.log("Received all trees-shas, fetching content..");
- var differ = new FileDiffer(this.base_tree, this.head_tree, this.orig_tree);
-
- differ.fetch(this, this.render);
- }
-
- printMe ( )
- {
- for (var key in this)
- console.log("key: " + key);
- }
-
- fetchTreeFromCommit ( commit, name, usercb )
- {
- console.log("Fetching " + name + " " + commit);
- var receiveCommit = function ( )
- {
- var response = JSON.parse(this.responseText);
-
- console.log("Received " + this.commit_name);
- this.outside[this.commit_name] = new FileTree(response.tree.sha, response.tree.url, "");
-
- this.usercb.call(this.outside);
- };
-
- var request = new XMLHttpRequest();
-
- request.outside = this;
- request.onload = receiveCommit;
- request.commit_name = name;
- request.usercb = usercb;
-
- // Initialize a request
- request.open('get', "https://api.github.com/repos/"+this.owner+"/"+this.repo+"/git/commits/" + commit);
-
- request.setRequestHeader("Authorization", "Basic " + btoa(this.usertoken));
- // Send it
- request.send();
- }
-
- // Generate the diff, append the elements to this.element
- render ( differ )
- {
- "use strict";
-
- var contents = this.element.getElementsByClassName("file");
- var content = contents[0];
- content.innerHTML = "";
- content.style.backgroundColor = "white";
- content.style.textAlign = "center";
-
- for (var i = 0; i < differ.changed.length; i++)
- {
- var el = differ.changed[i];
-
- if ((el.head === null || el.head.type != "blob") &&
- (el.base === null || el.base.type != "blob"))
- continue;
-
- var base_content = el.base ? el.base.content : "";
- var head_content = el.head ? el.head.content : "";
-
- var fname = el.head ? el.head.path : el.base.path;
-
- var base = difflib.stringAsLines(base_content),
- newtxt = difflib.stringAsLines(head_content),
- sm = new difflib.SequenceMatcher(base, newtxt),
- opcodes = sm.get_opcodes(),
- contextSize = 5; //byId("contextSize").value;
-
- var filename = document.createElement("DIV");
- filename.className = "file-header";
- filename.innerText = fname;
-
- content.appendChild(filename);
- contextSize = contextSize || null;
-
- var diff = diffview.buildView({
- baseTextLines: base,
- newTextLines: newtxt,
- opcodes: opcodes,
- baseTextName: "Old",
- newTextName: "New",
- contextSize: contextSize,
- viewType: 0 // 0 for side-by-side
- });
-
- diff.className = diff.className + " blob-wrapper";
- diff.style.margin = "auto";
- diff.style.textAlign = "left";
- content.appendChild(diff);
- }
-
- var pos = content.getBoundingClientRect();
-
- content.style.left = "" + (-pos.left + 15) + "px";
- content.style.width = "" + (document.documentElement.clientWidth - 30) + "px";
-
- var close_link = document.createElement("A");
- close_link.href = "#" + this.element.id;
- close_link.onclick = function () { this.parentElement.parentElement.getElementsByClassName("btn")[0].onclick(); };
- close_link.innerText = "Close";
- content.appendChild(close_link);
- }
- }
-
- var fetcher = new Fetcher();
-
- var DefaultURLHelper = "Optional default hash data URL";
-
-
- function deleteYourself ( ) { this.outerHTML = ""; }
-
- // Renders a box with user/token fields and button to ask for credentials
- function askCredentials ( )
- {
- if(document.getElementById("github-credentials-box"))
- return;
-
- console.log("Asking credentials");
-
- var box = document.createElement("DIV");
- box.style.backgroundColor = "white";
- box.style.position = "fixed";
- box.style.border = "solid black 2px";
- box.style.zIndex = 999999;
- box.style.left = "40%";
- box.style.top = "40%";
- box.style.padding = "20px";
- box.id = "github-credentials-box";
-
- var textfield_user = document.createElement("INPUT");
- var textfield_token = document.createElement("INPUT");
- var textfield_hash_data_url = document.createElement("INPUT");
-
- textfield_user.type = "text";
-
- var user = GM_getValue("username");
- if (!user)
- user = "Username";
-
- textfield_user.value = user;
- textfield_user.id = "github-user";
-
- var token = GM_getValue("token");
- if (!token)
- token = "Github Token";
-
- textfield_token.type = "text";
- textfield_token.value = token;
- textfield_token.id = "github-token";
-
- var url = GM_getValue("hash_data_url");
- if (!url)
- url = DefaultURLHelper;
-
- textfield_hash_data_url.type = "text";
- textfield_hash_data_url.value = url;
- textfield_hash_data_url.id = "hash-data-url";
-
- var note = document.createElement("P");
- note.href = "https://github.com/settings/tokens";
- note.innerHTML = "The token required here can be created at <a href=\"https://github.com/settings/tokens\">your settings page</a>.<br>Required scope is 'repo'.";
-
- var button = document.createElement("BUTTON");
- button.className = "btn";
- button.innerText = "Save";
- button.style.margin = "5px";
- button.onclick = saveCredentials;
-
- box.appendChild(textfield_user);
- box.appendChild(textfield_token);
- box.appendChild(document.createElement("BR"));
- box.appendChild(textfield_hash_data_url);
- box.appendChild(button);
- box.appendChild(note);
-
- document.body.appendChild(box);
- }
-
- // saves the credentials and removes the box and the button
- function saveCredentials ( )
- {
- var user = document.getElementById("github-user");
- var token = document.getElementById("github-token");
- var hash_data_url = document.getElementById("hash-data-url");
-
- if (hash_data_url.value != DefaultURLHelper)
- {
- hash_data_url = hash_data_url.value.trim();
-
- if (hash_data_url.length > 0 && hash_data_url.substr(-1, 1) != "/")
- hash_data_url = hash_data_url + "/";
-
- GM_setValue("hash_data_url", hash_data_url);
- }
-
- GM_setValue("username", user.value.trim());
- GM_setValue("token", token.value.trim());
-
- var box = document.getElementById("github-credentials-box");
- box.outerHTML = "";
-
- fetchUpdates();
- }
-
- function getTimeline ( )
- {
- var timeline;
- var timeline_content;
-
- for (var i=0; i<discussion_bucket.children.length; i++)
- if (discussion_bucket.children[i].classname == "discussion-sidebar")
- continue;
- else
- timeline = discussion_bucket.children[i];
-
- for (i=0; i < timeline.children.length; i++)
- if (timeline.children[i].className == "discussion-timeline-actions")
- continue;
- else
- timeline_content = timeline.children[0];
-
- return timeline_content;
- }
-
-
- function getTimelineItems ( times_only, type )
- {
- var timeline_content = getTimeline();
-
- // Walks up the parent chain until the direct parent is timeline_content
- var findTopMostChild = function ( child )
- {
- var my_child = child;
-
- while (my_child.parentElement != timeline_content)
- my_child = my_child.parentElement;
-
- return my_child;
- };
-
- var times = timeline_content.getElementsByTagName("relative-time");
-
- var return_array = [];
- var last;
- var last_was_review = false;
-
- for (var o=0; o < times.length; o++)
- {
- var topmost = findTopMostChild(times[o]);
-
- if (topmost == last)
- continue;
-
- if (type == "review")
- {
- // Only review tags have this class
- var is_review = /discussion-item-review/g.test(topmost.className);
-
-
- if (!is_review)
- {
- last_was_review = false;
- continue;
- }
- }
- else if (type == "comment")
- {
- // Only comments have this class
- if (!/timeline-comment-wrapper/g.test(topmost.className))
- continue;
- }
-
- if (times_only)
- {
- var date = times[o].getAttribute("datetime");
- var parsed_date = Date.parse(date);
-
- // Collaps reviews that directly follow each other into one
- if (last_was_review)
- return_array[return_array.length-1] = parsed_date;
- else
- return_array.push(parsed_date);
- }
- else
- {
- // Collaps reviews that directly follow each other into one
- if (last_was_review)
- return_array[return_array.length-1] = topmost;
- else
- return_array.push(topmost);
- }
-
- last = topmost;
- last_was_review = true;
- }
-
- return return_array;
- }
-
- function makeTimelineEntry ( time, text, action, id )
- {
- console.log("Creating entry " + text + " " + id);
-
- var timeline_content = getTimeline();
-
- // Walks up the parent chain until the direct parent is timeline_content
- var findTopMostChild = function ( child )
- {
- var my_child = child;
-
- while (my_child.parentElement != timeline_content)
- my_child = my_child.parentElement;
-
- return my_child;
- };
-
- var times = timeline_content.getElementsByTagName("relative-time");
-
- var insert_before;
-
- // Find the right place in the timeline to insert
- for (var o=0; o < times.length; o++)
- {
- var date = times[o].getAttribute("datetime");
-
- // Ignore review discussion timestamps
- if (/discussion/.test(times[o].parentElement.getAttribute("href")))
- continue;
-
- if (Date.parse(date) > time)
- {
- insert_before = findTopMostChild(times[o]);
- break;
- }
- }
-
- // Construct item to insert
- var timeline_item = document.createElement("DIV");
- timeline_item.className = "discussion-item-header discussion-item";
-
- // Copied from github src code for push icon
- timeline_item.innerHTML = '<span class="discussion-item-icon"><svg aria-hidden="true" class="octicon octicon-repo-push" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M4 3H3V2h1v1zM3 5h1V4H3v1zm4 0L4 9h2v7h2V9h2L7 5zm4-5H1C.45 0 0 .45 0 1v12c0 .55.45 1 1 1h4v-1H1v-2h4v-1H2V1h9.02L11 10H9v1h2v2H9v1h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1z"></path></svg></span>';
- timeline_item.id = id;
- timeline_item.appendChild(document.createTextNode(text));
-
- var link = document.createElement("A");
-
- link.className = "btn btn-sm btn-outline";
- link.innerText = "View changes";
- link.onclick = function () { action(this); return false; };
- link.href = "#";
-
- timeline_item.appendChild(link);
-
- timeline_content.insertBefore(timeline_item, insert_before);
- }
-
- // Creates a button in the github sidebar in PRs
- function makeButton ( text, action, id )
- {
- var sidebar = document.getElementById("github-incremental-diffs-sidebar-item");
-
- var buttondiv = document.createElement("DIV");
- buttondiv.id = id;
-
- var button = document.createElement("A");
-
- button.appendChild(document.createTextNode(text));
- button.onclick = function () { action(); return false; };
- button.href = "#";
-
- buttondiv.appendChild(button);
- sidebar.appendChild(buttondiv);
- }
-
- // Fetches the sha heads from hash_data_url
- function fetchUpdates ( base_url )
- {
- var urlsplit = document.URL.split("/");
- var owner = urlsplit[3];
- var repo = urlsplit[4];
- var prid_and_anker = urlsplit[6].split("#");
-
- var prid = prid_and_anker[0];
-
- var url = base_url+owner+'/'+repo+'/' + prid + "?cachebust=" + new Date().getTime();
-
- console.log("Fetching updates from " + url);
-
- // Create a new request object
- GM_xmlhttpRequest({
- method: "GET",
- url: url,
- onload: function (response) {
- if (response.status == 200)
- injectTimeline(response.responseText);
- else
- console.log("No pushes found at "+url+": " + response.status);
- }});
- }
-
- /* Injects "Author pushed" events into the PR timeline
- *
- * Params:
- * shas = list of sha's and unix timestamp pairs. Sha and timestamp are separated by ";".
- * Each pair is separated by "\n"
- */
- function injectTimeline ( shas )
- {
- var sidebar = document.getElementsByClassName("discussion-sidebar")[0];
-
- if (sidebar.removeEventListener)
- {
- sidebar.removeEventListener ('DOMSubtreeModified', fetchDelayed);
- }
-
- var sha_list = shas.split("\n");
-
- var base, head, update;
-
- update = 1;
-
- function makeShowDiffFunc ( inner_base, inner_head )
- {
- var func = function ( item )
- {
- if (item.innerText == "Hide changes")
- {
- var elem = item.parentElement.getElementsByClassName("file")[0];
- elem.outerHTML = "";
-
- item.innerText = "View changes";
- return;
- }
-
- var cont = document.createElement("DIV");
- cont.className = "file";
- cont.innerHTML = "Loading...";
- cont.style.backgroundColor = "yellow";
-
- item.parentElement.appendChild(cont);
-
- var urlsplit = document.URL.split("/");
- var owner = urlsplit[3];
- var repo = urlsplit[4];
-
- item.innerText = "Hide changes";
-
- console.log("pressed.. " + inner_base + " " + inner_head);
-
- var prid_and_anker = document.URL.split("/")[6].split("#");
- var prid = prid_and_anker[0];
-
- fetcher.start(owner, repo, prid, inner_base, inner_head, item.parentElement);
- };
- return func;
- }
-
- var pairs = [];
-
- // Build pairs of commits to create diff from
- for (var i = 0; i < sha_list.length; i++)
- {
- if (sha_list[i].length === 0)
- continue;
-
- var sha_data = sha_list[i].split(";");
- var sha = sha_data[0];
- var time;
-
- if (sha_data[1] !== undefined)
- time = new Date(parseInt(sha_data[1]) * 1000);
-
- if (base === undefined)
- {
- base = sha;
- continue;
- }
-
- head = sha;
-
- var pair = {};
- pair.base = base;
- pair.head = head;
- pair.time = time;
-
- pairs.push(pair);
-
- base = head;
- }
-
- console.log("Pairs: " + pairs.length + " last: " + head);
-
- // Next, merge the pairs between reviews/comments
- var timeline_items = getTimelineItems(true, "review");
-
- console.log("Found " + timeline_items.length + " items");
-
- var base_pair = null;
-
- var merged_pairs = [];
- var merged_pair = {};
-
- var timeline_it = 0;
-
- // Only try to merge pairs if more than one exists
- if (pairs.length > 1)
- {
- for (i=0; i < pairs.length; i++)
- {
- // Find the first review that is right before newer than our current
- while (pairs[i].time.getTime() > timeline_items[timeline_it] &&
- timeline_it+1 < timeline_items.length &&
- pairs[i].time.getTime() > timeline_items[timeline_it+1])
- timeline_it++;
-
- //console.log("Comparing " + pairs[i].time + " > " + new Date(timeline_items[timeline_it]) + " " + i + " > " + timeline_it);
-
- if (pairs[i].time.getTime() > timeline_items[timeline_it])
- {
- if (base_pair === null)
- {
- console.log("Set base at " + i);
- base_pair = pairs[i];
- timeline_it++;
- continue;
- }
-
- console.log("Merging a pair");
-
- // And use the pair one before that as head
- var head_pair = pairs[i-1];
-
- merged_pair = {};
- merged_pair.base = base_pair.base;
- merged_pair.head = head_pair.head;
- merged_pair.time = head_pair.time;
-
- merged_pairs.push(merged_pair);
-
- base_pair = pairs[i];
-
- timeline_it++;
-
- if (timeline_it >= timeline_items.length)
- break;
-
- continue;
- }
- }
-
- // Merge any remaining pairs
- if (merged_pairs.length === 0 ||
- merged_pairs[merged_pairs.length-1].head != pairs[pairs.length-1].head)
- {
- merged_pair = {};
- merged_pair.base = base_pair.base;
- merged_pair.head = pairs[pairs.length-1].head;
- merged_pair.time = pairs[pairs.length-1].time;
-
- merged_pairs.push(merged_pair);
- }
- }
- else
- {
- merged_pairs = pairs;
- }
-
- console.log("Merged pairs: " + merged_pairs.length);
-
- for (i=0; i < merged_pairs.length; i++)
- {
- var it = merged_pairs[i];
-
- // Don't remake a button that already exists
- if (!document.getElementById("diffbutton-" + update))
- {
- var formatted_time = update;
-
- var addZero = function ( num )
- {
- if (num < 10)
- num = "0" + num;
-
- return num;
- };
-
- if (it.time !== undefined)
- formatted_time = it.time.getDate() + "." +
- addZero((it.time.getMonth()+1)) + "." +
- it.time.getFullYear() + " " +
- addZero(it.time.getHours()) + ":" +
- addZero(it.time.getMinutes());
-
- makeTimelineEntry(it.time.getTime(), "Author pushed code changes at " + formatted_time, makeShowDiffFunc(it.base, it.head), "diffbutton-" + update);
- }
-
- update++;
- }
-
- if (sidebar.addEventListener)
- {
- sidebar.addEventListener ('DOMSubtreeModified', fetchDelayed, false);
- }
- }
-
- function fetchDelayed ( )
- {
- // Don't fetch again if there are still diff buttons
- if (document.getElementById("diffbutton-1"))
- {
- return;
- }
-
- var sidebar = document.getElementsByClassName("discussion-sidebar")[0];
- sidebar.removeEventListener ('DOMSubtreeModified', fetchDelayed);
- setTimeout(fetchUpdates, 1000);
- }
-
- function render ( )
- {
- 'use strict';
-
- var need_setup = !GM_getValue("username") || !GM_getValue("token");
-
- var css_style = GM_getResourceText ("CSSDIFF");
- GM_addStyle (css_style);
-
- var sidebar = document.getElementById("partial-discussion-sidebar");
-
- if (sidebar !== null)
- {
- var item = document.createElement("DIV");
- item.className = "discussion-sidebar-item";
- item.id = "github-incremental-diffs-sidebar-item";
-
- var button = document.createElement("BUTTON");
- button.className = "btn btn-sm";
- button.type = "submit";
-
- button.appendChild(document.createTextNode("Incremental Diffs Setup"));
- button.onclick = askCredentials;
-
- item.appendChild(button);
-
- sidebar.appendChild(item);
-
- fetchBaseUrl();
- }
- }
-
- function fetchBaseUrl ( )
- {
- var baseUrlCb = function ( )
- {
- if (this.status == 404)
- {
- console.log("No project specific base URL, using global one: " + GM_getValue("hash_data_url"));
- fetchUpdates(GM_getValue("hash_data_url"));
- return;
- }
-
- var response = JSON.parse(this.responseText);
-
- var blobCb = function ( )
- {
- var resp = JSON.parse(this.responseText);
- var base_url = atob(resp.content);
-
- console.log("Found project specific base url " + base_url);
- fetchUpdates(base_url);
- };
-
- var request2 = new XMLHttpRequest();
- request2.onload = blobCb;
- request2.open('get', response.object.url);
- var usertoken = GM_getValue("username") + ":" + GM_getValue("token");
- request2.setRequestHeader("Authorization", "Basic " + btoa(usertoken));
- request2.send();
- };
-
- var request = new XMLHttpRequest();
-
- request.onload = baseUrlCb;
-
- var urlsplit = document.URL.split("/");
- var owner = urlsplit[3];
- var repo = urlsplit[4];
-
- // Initialize a request
- request.open('get', "https://api.github.com/repos/" + owner + "/" + repo + "/git/refs/meta/incremental-diff-url");
-
- var usertoken = GM_getValue("username") + ":" + GM_getValue("token");
- request.setRequestHeader("Authorization", "Basic " + btoa(usertoken));
- // Send it
- request.send();
- }
-
-
- (function()
- {
- var parts = document.URL.split("/");
-
- if (parts[5] == "pull")
- render();
- // This is required for this script to be run upon ajax load.. not sure why
- window.onbeforeunload = function()
- {
- console.log("window changed!");
- };
- })();