Update: With git v1.7.9.5, this hack below is not needed anymore. Gitweb comes with sidebyside diff.
Bonus: Simultaneously scroll left and right files with overflow.
Dependencies: jQuery
Usage: Simply include jQuery in gitweb/gitweb.cgi (within the head tag).
Future work: Going by this thread on kerneltrap.org, ‘side-by-side’ diff appears to have been on the team’s TODO list and never gotton around to being implemented. So, except for the simultaneous scrolling, I intend to port this to Perl and hope to get it integrated into Gitweb core. Simultaneous scrolling needs to be done in Javascript and the fact that external dependencies are frowned upon,
Note that we frown upon introducing extra dependencies for gitweb, unless they are optional, and best detected automatically.
scrolling needs to be handled separately in gitweb.js (the currently implementation depends on jQuery).
Gist: https://gist.github.com/1274726
// See http://saicharan.in/blog/2011/10/09/gitweb-with-sidebyside-diff/ | |
$(document).ready(function() { | |
// Assumption: rem for left file, add for right file. | |
// => This follows from the assumption that additions | |
// are possible only for the right file. | |
$(".patch").each( function( name ) { | |
$.divp = $('<div/>', { class: "patch", id: $(this).attr('id') }); // div patch | |
$.diva = $('<div/>', { class: "patch a", id: $(this).attr('id')+'-a' }); // div for left file | |
$.divb = $('<div/>', { class: "patch b", id: $(this).attr('id')+'-b' }); // div for right file | |
$.divn = $('<div/>', { class: "no-exist" }).html(' '); // div for non-existent lines | |
// Insert This seems to be the only way this works | |
$.diva_count = 0; | |
$.divb_count = 0; | |
$.diff_prev = 'none'; | |
$.adjustDiff = function() { | |
if( $.diff_prev.match(/^add$/) || $.diff_prev.match(/^rem$/) ) { | |
if( $.diva_count - $.divb_count < 0 ) { | |
for(i=0; i<$.divb_count-$.diva_count; i++) { | |
$.diva.append($.divn.clone()); | |
} | |
} else if( $.diva_count - $.divb_count > 0 ) { | |
for(i=0; i<$.diva_count-$.divb_count; i++) { | |
$.divb.append($.divn.clone()); | |
} | |
} | |
$.diva_count = 0; | |
} | |
} | |
// If div class is 'diff rem', append to left | |
// If div class is 'diff add', append to right | |
// if div class is simply 'diff', append to both. | |
// Hunks have - & + lines. | |
$(this).children().each( function(name, value){ | |
if( $(this).attr("class").match(/^diff$/) ) { // Plain block: Add to both sides | |
$.adjustDiff(); | |
$.divb_count = 0; | |
$.diva.append($(this)); | |
$.divb.append($(this).clone()); | |
$.diff_prev = 'none'; | |
} else if( $(this).attr("class").match(/^diff chunk_header$/) ) { // chunk header | |
$.diva.append($(this)); | |
$.divb.append($(this).clone()); | |
$.diff_prev = 'none'; | |
} else if( $(this).attr("class").match(/^diff rem$/) ) { // Remove block | |
if( $.diff_prev.match(/^add$/) ){ | |
$.adjustDiff(); | |
} | |
$.diva.append($(this)); | |
$.diva_count++; | |
$.diff_prev = 'rem'; | |
} else if( $(this).attr("class").match(/^diff add$/) ) { // Add block | |
$.divb.append($(this)); | |
if( $.diff_prev.match(/^rem$/) ) { | |
$.divb_count = 0; | |
} | |
$.divb_count++; | |
$.diff_prev = 'add'; | |
} else { // Not part of diff, but of preamble. | |
$.divp.append($(this)); | |
$.diff_prev = 'none'; | |
} | |
}); | |
$.adjustDiff(); // Cleanup, when necessary | |
$.divp.append( $.diva ); // Append diva, divb to divp | |
$.divp.append( $.divb ); | |
$(this).replaceWith($.divp); // Replace this with divp | |
// Iterating thru' all .patch divs again. Can be refactored. | |
$(".patch").filter(function() { // Enable simultaneous scroll | |
return this.id.match(/patch[1-9]+-./); | |
}).each( function() { | |
var ids = this.id.match(/patch([1-9]+)-(.)/); | |
var otherdiv = ( ids[2].match(/^a$/) ? "b" : "a" ); | |
var scrollid = "#patch" + ids[1] + "-" + otherdiv; | |
$(this).scroll( function() { | |
$(scrollid).scrollLeft( $(this).scrollLeft() ); | |
}); | |
}); | |
}); | |
}); |
/* Append this to gitweb-HOME/static/gitweb.css, or insert it using JS */ | |
div.patch.a { | |
width: 50%; | |
overflow-y: hidden; | |
overflow-x: auto; | |
padding-bottom: 10px; | |
position: relative; | |
float: left; | |
clear: both; | |
} | |
div.patch.b { | |
width: 50%; | |
overflow-y: hidden; | |
overflow-x: auto; | |
padding-bottom: 10px; | |
position: relative; | |
float: right; | |
clear: right; | |
} | |
div.patch { | |
clear: both; | |
} | |
div.page_footer { | |
clear: both; | |
} | |
div.diff.add { | |
background-color: #F6F9ED; | |
} | |
div.diff.rem { | |
background-color: #FEF1E9; | |
} | |
div.no-exist { | |
background-color: #EBECE4; | |
width: 100%; | |
} |