Utils = {
  loadTemplates: function() {
    var templates = {};
    $('*[type="text/x-js-template"]').map(function() {
      var name = $(this).attr('name');
      var source = $(this).html().replace('&gt;', '>');
      if(name.split('/')[1][0] == '_') { Handlebars.registerPartial(name.replace('/', ''), source) }
      templates[name] = Handlebars.compile(source);
    });
    return templates;
  },
  queryString: function(params) {
    if(!params) return '';
    var query = _.compact(_.map(params, function(value, key) { return value ? key + '=' + value : null }));
    return query.length > 0 ? '?' + query.join('&') : '';
  },
  duration: function(started, finished) {
    started  = new Date(started);
    finished = finished ? new Date(finished) : new Date();
    return started ? Math.round((finished - started) / 1000) : 0;
  },
  activateTab: function(element, tab) {
    $('.tabs li', element).removeClass('active');
    $('#tab_' + tab.toLowerCase(), element).addClass('active');
  },
  animated: function(element) {
    return !!element.queue()[0];
  },
  flash: function(element) {
    if(!element.length == 0 && !Utils.animated(element)) {
      Utils._flash(element);
    }
  },
  _flash: function(element) {
    element.effect('highlight', {}, 1000, function () {
      Utils._flash(element)
    });
  },
  unflash: function(element) {
    if(!element.length == 0) {
      element.stop().css({ 'background-color': '', 'background-image': '' });
    }
  },
  filterLog: function(string) {
    // string = Handlebars.Utils.escapeExpression(string);
    string = Utils.stripPaths(string);
    string = Utils.foldLog(string);
    string = Deansi.parse(string);
    return string;
  },
  stripPaths: function(string) {
    return string.replace(/\/tmp\/travis\/builds(\/[^\/]+){2}\//gm, '');
  },
  foldLog: function(string) {
    string = Utils.unfoldLog(string);
    var folds = {
      // 'clone': /(^|<\/div>)(\$ git clone.*\r?\n(?:(Initialized|remote:|Receiving|Resolving).*?\r?\n)*)/m,
      'clean':   /(^|<\/div>)(\$ git clean.*\r?\n(?:Removing .*\r?\n)+\r?\n*)/gm,
      'fetch':   /(^|<\/div>)(\$ git fetch.*\r?\nFrom .*\n.*)\r?\n/gm,
      'bundle':  /(^|<\/div>)(\$ bundle install.*\r?\n*(?:(Fetching|Updating|Using|Installing|remote:|Receiving|Resolving).+\r?\n*)*)/gm,
      'migrate': /(^|<\/div>)(\$ rake db:migrate[\s\S]*(?:^== +\w+: migrated \(.*\) =+\r?\n))\r?\n?/gm,
      'exec':    /(^|<\/div>)(\/home\/([^\/]+)\/.rvm\/rubies\/\S*?(ruby|rbx|jruby).*?)\r?\n/gm
    };
    _.each(folds, function(fold, type) {
      string = string.replace(fold, function() { return arguments[1] + '<div class="fold ' + type + '">' + arguments[2].trim() + '</div>'; });
    });
    string = string.replace(/([\.-_*SEF]{120})\n?/g, '$1\n')
    return string;
  },
  unfoldLog: function(string) {
    return string.replace(/<div class="fold">([\s\S]*?)<\/div>/mg, '$1\n');
  },
  updateTimes: function(element) {
    $('.timeago', element).timeago();
    $('.duration', element).readableTime();

    if(!Utils._updateTimesInterval) {
      Utils._updateTimesInterval = setInterval(function() { Utils.updateTimes() }, 3000);
    }
  },
  readableTime: function(duration) {
    var days    = Math.floor(duration / 86400)
    var hours   = Math.floor(duration % 86400 / 3600);
    var minutes = Math.floor(duration % 3600 / 60);
    var seconds = duration % 60;

    if(days > 0) {
      return 'more than 24 hrs';
    } else {
      var result = [];
      if(hours   > 0) { result.push(hours + ' hrs'); }
      if(minutes > 0) { result.push(minutes + ' min'); }
      if(seconds > 0) { result.push(seconds + ' sec'); }
      return result.length > 0 ? result.join(' ') : '-';
    }
  },
  updateGithubStats: function(repository, element) {
    if(!window.__TESTING__) {
      $.getJSON('http://github.com/api/v2/json/repos/show/' + repository.get('slug') + '?callback=?', function(data) {
        var url = 'http://github.com/' + repository.get('slug');
        element.find('.watchers').attr('href', url + '/watchers').text(data.repository.watchers);
        element.find('.forks').attr('href', url + '/network').text(data.repository.forks);
      });
    }
  }
}

function trace() {
  try {
    i.dont.exist; // force an exception
  } catch(e) {
    var lines = e.stack.split('\n').slice(2);
    var stack = _.map(lines, function(line) { return line.replace(/^\s*at/, ''); });
    console.log('trace -------------------------------');
    _.each(stack, function(line) { console.log(line); });
  }
}


@@ -1781,16 +1781,18 @@
 s(%5C/%5B%5E%5C/
+%5Cn
 %5D+)%7B2%7D%5C/
@@ -1793,17 +1793,16 @@
 )%7B2%7D%5C//g
-m
 , '');%0A 
@@ -2005,16 +2005,19 @@
 m,%0A     
+ //
  'clean'
@@ -2022,32 +2022,33 @@
 n':   /(%5E%7C%3C%5C
+%5C
 /div%3E)(%5C
 $ git clean.
@@ -2027,32 +2027,33 @@
  /(%5E%7C%3C%5C%5C/div%3E)(%5C
+%5C
 $ git clean.*%5Cr?
@@ -2050,19 +2050,21 @@
 clean.*%5C
+%5C
 r?%5C
+%5C
 n(?:Remo
@@ -2075,18 +2075,22 @@
  .*%5C
+%5C
 r?%5C
+%5C
 n)+%5C
+%5C
 r?%5C
+%5C
 n*)/
@@ -2098,16 +2098,19 @@
 m,%0A     
+ //
  'fetch'
@@ -2115,32 +2115,33 @@
 h':   /(%5E%7C%3C%5C
+%5C
 /div%3E)(%5C
 $ git fetch.
@@ -2124,24 +2124,25 @@
 %7C%3C%5C%5C/div%3E)(%5C
+%5C
 $ git fetch.
@@ -2143,19 +2143,21 @@
 fetch.*%5C
+%5C
 r?%5C
+%5C
 nFrom .*
@@ -2161,16 +2161,19 @@
  .*%5C
+%5C
 n.*)%5C
+%5C
 r?%5C
+%5C
 n/gm

make stripPaths regex less greedy