├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── git-log-class.coffee ├── git-log-view.js ├── git-log.js ├── git-repo-list.coffee ├── gitgraph.js └── logparser.js ├── package.json ├── resources └── git-log.gif └── styles └── git-log.less /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | tmp 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 - First Release 2 | * Every feature added 3 | * Every bug fixed 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nikhil Kalige 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Log Package 2 | 3 | # Not maintained anymore. 4 | 5 | Git-log is a package for [Atom][atom] that creates a graph of your git commits and shows commit related information for you on the editor. 6 | 7 | ![](https://raw.githubusercontent.com/NikhilKalige/git-log/master/resources/git-log.gif) 8 | 9 | ## Usage 10 | 11 | Open the command palette with `Cmd-Shift-P` (MacOS) or `Ctrl-Shift-P` (Windows or Linux) 12 | 13 | >- `Git Log: Show` 14 | 15 | ## Installing 16 | 17 | Use the Atom package manager, which can be found in the Settings view or 18 | run `apm install git-log` from the command line. 19 | 20 | ## Feature requests & bug reports 21 | 22 | The main development branch is `develop` and the stable 'production' branch is `master`. Please remember to base your branch from `develop` and issue the pull request back to that branch. 23 | 24 | ### Note 25 | The inspiration for this package is SourceTree App. 26 | 27 | [atom]: http://atom.io/ 28 | -------------------------------------------------------------------------------- /lib/git-log-class.coffee: -------------------------------------------------------------------------------- 1 | {$$, ScrollView, View} = require 'atom-space-pen-views' 2 | 3 | module.exports = 4 | 5 | class GitLogView extends View 6 | @content: -> 7 | @div class: 'git-log', tabindex: -1, => 8 | @subview 'main_panel', new MainPanelView 9 | @subview 'info_panel', new InfoPanelView 10 | 11 | constructor: -> 12 | super 13 | 14 | 15 | class MainPanelView extends ScrollView 16 | @content:-> 17 | @div class: 'main panels',cellpadding: 0, cellspacing: 0, border: 0, outlet: 'main_panel', => 18 | @table => 19 | @div class: 'graph', outlet: 'graph' 20 | @thead => 21 | @tr => 22 | @th class: 'graph-col', => 23 | @p 'Graph' 24 | @th class: 'comments', outlet: 'comments', => 25 | @p 'Description' 26 | @th class: 'commit', outlet: 'commit', => 27 | @p 'Commit' 28 | @th class: 'date', outlet: 'date', => 29 | @p 'Date' 30 | @th class: 'author', outlet: 'author', => 31 | @p 'Author' 32 | @tbody outlet: 'body' 33 | 34 | initialize: -> 35 | super 36 | 37 | 38 | class InfoPanelView extends ScrollView 39 | @content: -> 40 | @div class: 'info panels', => 41 | @div class: 'info-data', outlet: 'info_data' 42 | @div class: 'info-image', outlet: 'info_image' 43 | @div class: 'info-file', outlet: 'info_file', => 44 | @table => 45 | @thead => 46 | @tr => 47 | @th class: 'stat', outlet:'status', => 48 | @p 'Status' 49 | @th class: 'file', outlet: 'name', => 50 | @p 'Filename' 51 | @th class: 'path', outlet: 'path', => 52 | @p 'Path' 53 | @th class: 'add', outlet: 'addition', => 54 | @p 'Addition' 55 | @th class: 'del', outlet: 'deletion', => 56 | @p 'Deletion' 57 | @tbody outlet: 'body' 58 | 59 | add_content: (head, content) -> 60 | @info_data.append $$ -> 61 | @h2 => 62 | @text head 63 | @span content 64 | 65 | ### 66 | class MainPanelView extends ScrollView 67 | @content:-> 68 | @div class: 'main panels', => 69 | @subview 'graph', new ColumnView('Graph', 'graph') 70 | @div class: 'table', outlet: 'table', => 71 | @subview 'comments', new ColumnView('Description', 'comments', true) 72 | @subview 'commit', new ColumnView('Commit', 'commit', true) 73 | @subview 'date', new ColumnView('Date', 'date', true) 74 | @subview 'author', new ColumnView('Author', 'author') 75 | 76 | 77 | class InfoPanelView extends ScrollView 78 | @content: -> 79 | @div class: 'info panels', => 80 | @div class: 'info-data', outlet: 'info_data' 81 | @div class: 'info-image', outlet: 'info_image' 82 | @div class:'info-file', outlet: 'info_file', => 83 | @subview 'status', new ColumnView('Status', 'status') 84 | @subview 'name', new ColumnView('Filename', 'file') 85 | @subview 'path', new ColumnView('Path', 'path') 86 | @subview 'addition', new ColumnView('Addition', 'add') 87 | @subview 'deletion', new ColumnView('Deletion', 'del') 88 | 89 | add_content: (head, content) -> 90 | @info_data.append $$ -> 91 | @h2 => 92 | @text head 93 | @span content 94 | 95 | 96 | class ColumnView extends View 97 | @content: (title, class_name, resizable) -> 98 | @div class: 'column ' + class_name, => 99 | @div class: 'list-head', => 100 | @h2 title 101 | @div class:'resize-handle' if resizable 102 | @div class: 'list', outlet: 'list' 103 | 104 | add_content: (content) -> 105 | @list.append $$ -> 106 | @p => 107 | @span content 108 | ### 109 | -------------------------------------------------------------------------------- /lib/git-log-view.js: -------------------------------------------------------------------------------- 1 | var GitLogView = require("./git-log-class"); 2 | var BufferedProcess = require("atom").BufferedProcess; 3 | var LogParser = require("./logparser"); 4 | var GitGraph = require("./gitgraph"); 5 | var $ = require("atom-space-pen-views").$; 6 | 7 | var __bind = function(fn, me) { 8 | return function() { 9 | return fn.apply(me, arguments); 10 | }; 11 | }; 12 | 13 | var safe_tags = function(str) { 14 | return str.replace(/&/g,'&').replace(//g,'>') ; 15 | }; 16 | 17 | GitLogView.prototype.initialize = function(repo) { 18 | var editorFontSize = atom.config.get('editor.fontSize'); 19 | var editorLineHeight = atom.config.get('editor.lineHeight'); 20 | var fontScale = atom.config.get('git-log.fontScale'); 21 | this.font_family = atom.config.get('git-log.fontFamily'); 22 | this.repo = repo; 23 | this.info_panel.hide(); 24 | this.path = repo.repo_name; 25 | 26 | this.font_size = editorFontSize * fontScale; 27 | this.line_height = Math.round(this.font_size * editorLineHeight); 28 | 29 | this.get_log(); 30 | 31 | //this.resize_started = __bind(this.resize_started, this); 32 | //this.resize_table = __bind(this.resize_table, this); 33 | //this.resize_stopped = __bind(this.resize_stopped, this); 34 | this.hide = __bind(this.info_panel.hide, this); 35 | } 36 | 37 | GitLogView.prototype.get_log = function() { 38 | this.log = null; 39 | var concat = function(self) { 40 | return function(data) { 41 | if(self.log == null) { 42 | self.log = ""; 43 | } 44 | self.log += data; 45 | } 46 | }(this); 47 | 48 | var display_log = function(self) { 49 | return function(data) { 50 | self.log_callback(); 51 | } 52 | }(this); 53 | 54 | var args = ['log', '--decorate=full', '--date=default', '--pretty=fuller', '--all', '--parents', '--numstat', '--topo-order', '--raw']; 55 | var options = {}; 56 | options.cwd = this.repo.getWorkingDirectory(); 57 | 58 | return new BufferedProcess({ 59 | command: 'git', 60 | args: args, 61 | options: options, 62 | stdout: concat, 63 | stderr: function(data) {console.log(data.toString())}, 64 | exit: display_log 65 | }); 66 | }; 67 | 68 | GitLogView.prototype.log_callback = function(data) { 69 | this.parser(); 70 | 71 | //this.min_width = Math.floor(this.width()/10); 72 | 73 | this.fill_content(); 74 | /** 75 | this.on('mousedown', '.resize-handle', (function(self) { 76 | return function(e) { 77 | self.resize_started(e); 78 | }; 79 | })(this)); 80 | **/ 81 | atom.commands.add('.git-log', 'core:cancel', (function(self) { 82 | return function(e) { 83 | self.info_panel.hide(); 84 | }; 85 | })(this)); 86 | 87 | atom.commands.add('.git-log', 'core:close', (function(self) { 88 | return function(e) { 89 | self.info_panel.hide(); 90 | }; 91 | })(this)); 92 | 93 | atom.commands.add('.git-log', 'core:move-up', (function(self) { 94 | return function(e) { 95 | if((self.previous_line == null) || (self.previous_line == 1)) 96 | return; 97 | 98 | var data = self.get_target_line(null, self.previous_line - 1); 99 | self.handle_scroll(data[0], 1); 100 | self.select_display_info(data[0], data[1]); 101 | }; 102 | })(this)); 103 | 104 | atom.commands.add('.git-log', 'core:move-down', (function(self) { 105 | return function(e) { 106 | if((self.previous_line == null) || (self.previous_line == self.log.length)) 107 | return; 108 | 109 | var data = self.get_target_line(null, self.previous_line + 1); 110 | self.handle_scroll(data[0], 0); 111 | self.select_display_info(data[0], data[1]); 112 | }; 113 | })(this)); 114 | }; 115 | 116 | GitLogView.prototype.parser = function() { 117 | this.log = LogParser(this.log); 118 | var graph = new GitGraph(this.main_panel.graph, this.log, this.line_height, 2); 119 | }; 120 | 121 | GitLogView.prototype.fill_content = function() { 122 | var create_main_row = function(log) { 123 | var html = '

 

'; 124 | html += '

' + log.message.split('\n')[0] + '

'; 125 | html += '

' + log.sha1.slice(0, 7)+ '

'; 126 | 127 | var date = log.author_date.split(/ /); 128 | 129 | html += '

' + date[2] + ' ' + date[1] + ' ' + date[4] + ' ' + date[3].slice(0,5) + '

' 130 | html += '

' + log.author_name + '

' 131 | html += '' 132 | 133 | return html; 134 | }; 135 | 136 | var set_widths = function(svg_width) { 137 | var total = main_panel.width(); 138 | var diff = total - svg_width; 139 | 140 | // allocate widths in this ratio 70:10:10:10 141 | main_panel.comments.width(diff * .7); 142 | diff = diff * 0.1; 143 | main_panel.commit.width(diff); 144 | main_panel.date.width(diff); 145 | main_panel.author.width(diff); 146 | } 147 | 148 | var i, len; 149 | var main_panel = this.main_panel; 150 | for(i=0; len = this.log.length, i < len; i++) { 151 | var log = this.log[i]; 152 | main_panel.body.append(create_main_row(log)); 153 | } 154 | 155 | this.css({ 156 | 'font-family': this.font_family, 157 | 'font-size': this.font_size + 'px', 158 | 'line-height': this.line_height + 'px' 159 | }); 160 | 161 | main_panel.graph.css('top', main_panel.find('thead').height()); 162 | 163 | var svg_width = main_panel.graph.width(); 164 | main_panel.find('thead th:first-child').width(svg_width); 165 | set_widths(svg_width); 166 | 167 | main_panel.body.on("click", "tr", (function(self) { 168 | return function(e) { 169 | var data = self.get_target_line(e); 170 | self.select_display_info(data[0], data[1]); 171 | }; 172 | })(this)); 173 | } 174 | 175 | GitLogView.prototype.getTitle = function() { 176 | return "Git-log: " + this.path; 177 | }; 178 | 179 | GitLogView.prototype.getURI = function() { 180 | return "git-log://" + this.path; 181 | }; 182 | 183 | GitLogView.prototype.onDidChangeTitle = function() { 184 | return; 185 | }; 186 | 187 | GitLogView.prototype.onDidChangeModified = function() { 188 | return; 189 | }; 190 | 191 | GitLogView.prototype.get_image = function(email) { 192 | var crypto = require('crypto'); 193 | var base = "http://www.gravatar.com/avatar/"; 194 | return base + crypto.createHash('md5').update(email.toLowerCase().trim()).digest('hex') + "?s=64"; 195 | }; 196 | 197 | GitLogView.prototype.resize_started = function(event) { 198 | this.pos={}; 199 | this.pos.pointer_x = event.pageX; 200 | this.pos.left = $(event.target).position().left; 201 | 202 | this.pos.left_col = $(event.target).parents('.column'); 203 | this.pos.right_col = $(this.pos.left_col.next()); 204 | 205 | this.pos.left_width = this.pos.left_col.width(); 206 | this.pos.right_width = this.pos.right_col.width(); 207 | 208 | $(document).on('mousemove.git-log', this.resize_table); 209 | $(document).on('mouseup.git-log', this.resize_stopped); 210 | return false; 211 | }; 212 | 213 | GitLogView.prototype.resize_table = function(event) { 214 | if(!this.pos) 215 | return; 216 | 217 | var x = event.pageX - this.pos.pointer_x + this.pos.left; 218 | this.pos.x = x; 219 | 220 | var inc = this.pos.x - this.pos.left; 221 | 222 | var w = this.pos.left_width + inc; 223 | var w2 = this.pos.right_width - inc; 224 | 225 | w = Math.max(this.min_width, w); 226 | w2 = Math.max(this.min_width, w2); 227 | 228 | //this.pos.left_col.width(w + 'px'); 229 | //this.pos.right_col.width(w2 + 'px'); 230 | 231 | this.pos.left_col.css('flex-basis', w + 'px'); 232 | this.pos.right_col.css('flex-basis', w2 + 'px'); 233 | return false; 234 | } 235 | 236 | GitLogView.prototype.resize_stopped = function(event) { 237 | this.pos = null; 238 | $(document).off('mousemove.git-log', this.resize_table); 239 | $(document).off('mouseup.git-log', this.resize_stopped); 240 | } 241 | 242 | GitLogView.prototype.get_target_line = function(event, line) { 243 | var line_no, target; 244 | if(event == null) { 245 | line_no = line; 246 | target = this.main_panel.body.find('tr:nth-child(' + line_no + ')'); 247 | } 248 | else { 249 | line_no = $(event.currentTarget).index() + 1; 250 | target = $(event.currentTarget); 251 | } 252 | return [target, line_no]; 253 | }; 254 | 255 | GitLogView.prototype.handle_scroll = function(target, dir) { 256 | var offset_trigger = 3; 257 | var line_height = $(target).height() + 2; // accounting for the margin 258 | offset_trigger = offset_trigger * line_height; 259 | var window_height = this.main_panel.height(); 260 | // move up 261 | if(dir == 1) { 262 | if(target.offset().top < offset_trigger) { 263 | this.main_panel.scrollTop(this.main_panel.scrollTop() - line_height); 264 | } 265 | } 266 | 267 | else if(dir == 0) { 268 | if(target.offset().top > (window_height - offset_trigger)) { 269 | this.main_panel.scrollTop(this.main_panel.scrollTop() + line_height); 270 | } 271 | } 272 | }; 273 | 274 | GitLogView.prototype.select_display_info = function(target, line_no) { 275 | if(!this.info_panel.isVisible()) 276 | this.info_panel.show(); 277 | 278 | this.info_panel.info_data.empty(); 279 | this.info_panel.info_image.empty(); 280 | this.info_panel.body.empty(); 281 | 282 | var commit_data = this.log[line_no - 1]; 283 | 284 | // background line higlighting 285 | if(this.previous_line != null) 286 | this.main_panel.body.find('tr:nth-child(' + this.previous_line + ')') 287 | .removeClass('log-highlight'); 288 | 289 | this.previous_line = line_no; 290 | target.addClass('log-highlight'); 291 | 292 | this.info_panel.add_content("Commit:", commit_data.sha1 + " [" + commit_data.sha1.slice(0,7) +"]" ); 293 | this.info_panel.add_content("Parents:", commit_data.parents.map(function(str) { 294 | return str.slice(0,10); 295 | }).join(", ") 296 | ); 297 | this.info_panel.add_content("Author:", commit_data.author_name + ' <' + commit_data.author_email + '>'); 298 | var date = commit_data.author_date.split(/ /); 299 | this.info_panel.add_content("Date:", date[2] + ' ' + date[1] + ' ' + date[4] + ' ' + date[3].slice(0,8)) 300 | 301 | // add committer related information 302 | if(commit_data.committer_name != commit_data.author_name) { 303 | this.info_panel.add_content("Committer:", commit_data.committer_name + ' <' + commit_data.committer_email + '>'); 304 | date = commit_data.commit_date.split(/ /); 305 | this.info_panel.add_content("Commit Date:", date[2] + ' ' + date[1] + ' ' + date[4] + ' ' + date[3].slice(0,8)) 306 | } 307 | 308 | // remove refs/heads or refs/remotes 309 | if(commit_data.refs.length > 0) 310 | this.info_panel.add_content("Labels:", commit_data.refs.map(function(str) { 311 | return str.replace(/^.*\/(remotes|heads)\//,''); 312 | }).join(", ")); 313 | this.info_panel.info_data.append('

' + safe_tags(commit_data.message).replace(/\n/g, '
') + "

"); 314 | this.info_panel.info_image.append('') 315 | 316 | if(commit_data.committer_name != commit_data.author_name) { 317 | this.info_panel.info_image.append('') 318 | } 319 | 320 | // fill the file information 321 | var i, len, temp, status_text; 322 | 323 | var create_row = function(temp) { 324 | var temp, status_text, index; 325 | var html = ''; 326 | 327 | if(temp[3] == 'A') 328 | status_text = 'added'; 329 | else if(temp[3] == 'D') 330 | status_text = 'deleted'; 331 | else 332 | status_text = 'modified'; 333 | html += '

' + status_text + '

'; 334 | 335 | index = temp[2].lastIndexOf('/'); 336 | html += '

' + temp[2].slice(index + 1) + '

'; 337 | html += '

' + ((index >= 0) ? temp[2].slice(0, index) : ' ') + '

' 338 | html += '

' + temp[0] + '

'; 339 | html += '

' + temp[1] + '

'; 340 | html += ''; 341 | 342 | return html; 343 | }; 344 | 345 | this.info_panel.info_file.show(); 346 | if(commit_data.file_line_diffs.length == 0) 347 | this.info_panel.info_file.hide(); 348 | 349 | var i, len, temp; 350 | for(i=1; len=commit_data.file_line_diffs.length, i 0) { 23 | var repo_list = []; 24 | var name; 25 | 26 | for(var i=0; i 8 | super 9 | @addClass('modal overlay from-top') 10 | @storeFocusedElement() 11 | @panel = atom.workspace.addModalPanel(item: this, visible: true) 12 | @panel.show() 13 | @setItems(@listOfItems) 14 | @focusFilterEditor() 15 | 16 | getFilterKey: -> 17 | 'repo_name' 18 | 19 | viewForItem: (item) -> 20 | $$ -> @li(item.repo_name) 21 | 22 | cancelled: -> 23 | @panel.hide() 24 | @panel.destroy() 25 | 26 | confirmed: (item) -> 27 | @cancel() 28 | options= { 29 | 'repo': item 30 | }; 31 | uri = "git-log://" + item.repo_name 32 | old_pane = atom.workspace.paneForURI(uri) 33 | old_pane.destroyItem old_pane.itemForURI(uri) if old_pane 34 | atom.workspace.open uri, options 35 | -------------------------------------------------------------------------------- /lib/gitgraph.js: -------------------------------------------------------------------------------- 1 | //var d3 = require('d3'); 2 | 3 | 4 | var is_empty = function(obj) { 5 | if (obj == null) return true; 6 | if (obj.length > 0) return false; 7 | if (obj.length === 0) return true; 8 | for (var key in obj) { 9 | if (hasOwnProperty.call(obj, key)) return false; 10 | } 11 | return true; 12 | }; 13 | 14 | function GitGraph(location, data, line_height, margin) { 15 | this.config = this.set_config(line_height, margin); 16 | this.set_position(data); 17 | var svg = this.render(location, data); 18 | } 19 | 20 | GitGraph.prototype.set_config = function(line_height, margin) { 21 | var config = {}; 22 | var circle_radius_percent = 20; 23 | var circle_stroke_percent = 8; 24 | var branch_spacing_percent = 60; 25 | var line_width_percent = 10; 26 | 27 | var percentage = function(percent) { 28 | return (line_height * percent)/100; 29 | } 30 | 31 | config.circle = {}; 32 | config.circle.radius = percentage(circle_radius_percent); 33 | config.circle.stroke = percentage(circle_stroke_percent); 34 | config.circle_spacing = line_height + margin; 35 | config.branch_spacing = percentage(branch_spacing_percent); 36 | config.line_width = percentage(line_width_percent); 37 | config.left_margin = line_height / 2; 38 | config.top_margin = line_height / 2; 39 | config.cross_height = 40/100; 40 | /*config.color_list = [ 41 | "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", 42 | "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", 43 | "#bcbd22", "#dbdb8d","#17becf", "#9edae5" 44 | ]; 45 | config.color_list = [ 46 | "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", 47 | "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", 48 | "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" 49 | ]; 50 | config.color_list = [ 51 | "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", 52 | "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", 53 | "#636363", "#969696", "#bdbdbd", "#d9d9d9" 54 | ];*/ 55 | config.color_list = [ 56 | "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", 57 | "#bcbd22", "#17becf" 58 | ]; 59 | return config; 60 | }; 61 | 62 | GitGraph.prototype.set_position = function(data) { 63 | var _i, _len; 64 | var curr_row, curr_col; 65 | var branches = []; 66 | this.max_level = 0; 67 | 68 | curr_row = curr_col = 0; 69 | 70 | var get_free_column = function() { 71 | var i, len; 72 | for(i=0; len = branches.length, i < len; i++) { 73 | if(branches[i] == null) 74 | return i; 75 | } 76 | return len; 77 | } 78 | 79 | var update_branch = function(parent, col) { 80 | branches[col] = parent; 81 | } 82 | 83 | var create_branch = function(commit) { 84 | var i, len, index, my_col, par_col; 85 | var par, sha1; 86 | 87 | if((index = branches.indexOf(commit.sha1)) > -1) { 88 | my_col = index; 89 | } 90 | else { 91 | my_col = get_free_column(); 92 | } 93 | while((index = branches.indexOf(commit.sha1)) > -1) { 94 | branches[index] = null; 95 | } 96 | 97 | for(i=0; len = commit.parents.length, i < len; i++) { 98 | par = commit.parents[i]; 99 | 100 | if((index = branches.indexOf(par)) > -1) { 101 | if((branches[my_col] == null)) 102 | update_branch(par, my_col); 103 | } 104 | else { 105 | if(len == 1 || i == 0) { 106 | // dont create new branch 107 | update_branch(par, my_col); 108 | } 109 | else { 110 | par_col = get_free_column(); 111 | update_branch(par, par_col); 112 | } 113 | } 114 | } 115 | return my_col; 116 | } 117 | 118 | for(_i=0; _len = data.length, _i < _len; _i++) { 119 | var commit = data[_i]; 120 | var position = {}; 121 | 122 | position.column = create_branch(commit); 123 | if(position.column > this.max_level) 124 | this.max_column = position.column; 125 | position.row = curr_row++; 126 | commit.position = position; 127 | } 128 | this.max_row = curr_row; 129 | } 130 | 131 | GitGraph.prototype.commit_search = function(data, commit) { 132 | var i, len; 133 | for(i=0; len = data.length, i < len; i++) { 134 | if(data[i].sha1 === commit) 135 | return i; 136 | } 137 | }; 138 | 139 | GitGraph.prototype.lines = function(data) { 140 | var create_mid_point = function(start, end, height) { 141 | var point = {}; 142 | if(start.x == end.x) { 143 | return false; 144 | } 145 | else if(start.x < end.x) { 146 | point.x = end.x; 147 | point.y = start.y + height; 148 | } 149 | else { 150 | point.x = start.x; 151 | point.y = end.y - height; 152 | } 153 | return point; 154 | }; 155 | 156 | var i, len; 157 | var height = this.config.cross_height; 158 | var colors = this.config.color_list; 159 | var line_array = []; 160 | var line_color = []; 161 | for(i=0; len = data.length, i < len; i++) { 162 | var commit, j, _len; 163 | commit = data[i]; 164 | // assign color for commit 165 | if(line_color[commit.position.column] == null) { 166 | clr = colors.shift(); 167 | colors.push(clr); 168 | line_color[commit.position.column] = clr; 169 | } 170 | else { 171 | clr = line_color[commit.position.column]; 172 | } 173 | commit.position.color = clr; 174 | 175 | for(j=0; _len = commit.parents.length, j < _len; j++) { 176 | var line = []; 177 | var start, mid, end, clr; 178 | start={}; mid={}; end={}; 179 | 180 | var index = this.commit_search(data, commit.parents[j]); 181 | var parent = data[index]; 182 | 183 | start.x = commit.position.column; 184 | start.y = commit.position.row; 185 | end.x = parent.position.column; 186 | end.y = parent.position.row; 187 | 188 | if(start.x < end.x) { 189 | 190 | mid.x = end.x; 191 | mid.y = start.y + height; 192 | clr = colors.shift(); 193 | colors.push(clr); 194 | 195 | line_color[end.x] = clr; 196 | } 197 | else if(start.x > end.x) { 198 | mid.x = start.x; 199 | mid.y = end.y - height; 200 | line_color[start.x] = null; 201 | } 202 | start.color = clr; 203 | line.push(start); 204 | if(is_empty(mid) == false) 205 | line.push(mid); 206 | line.push(end); 207 | line_array.push(line); 208 | } 209 | } 210 | return line_array; 211 | }; 212 | 213 | GitGraph.prototype.render = function(location, data) { 214 | var line_array = this.lines(data); 215 | var self = this; 216 | 217 | var create_line = function(d) { 218 | var x, y, point_no; 219 | var line; 220 | point_no = 1; 221 | for(var i=0; len = d.length, i < len; i++) { 222 | x = self.config.left_margin + (d[i].x * self.config.branch_spacing); 223 | y = self.config.top_margin + (d[i].y * self.config.circle_spacing) 224 | if(point_no == 1) { 225 | line = "M" + x + ',' + y; 226 | point_no++; 227 | } 228 | else { 229 | line+= "L" + x + ',' + y; 230 | } 231 | } 232 | return line; 233 | }; 234 | 235 | var create_element = function(type) { 236 | return document.createElementNS("http://www.w3.org/2000/svg", type); 237 | }; 238 | 239 | var height = this.config.top_margin + this.max_row * this.config.circle_spacing; 240 | 241 | var svg = create_element("svg"); 242 | 243 | var line_group = svg.appendChild(create_element("g")); 244 | var circle_group = svg.appendChild(create_element("g")); 245 | 246 | circle_group.setAttribute("stoke-width", self.config.circle.stroke); 247 | circle_group.setAttribute("stroke", "#000"); 248 | 249 | data.forEach(function(d) { 250 | var circle = circle_group.appendChild(create_element("circle")); 251 | circle.setAttribute("cx", (self.config.left_margin + (d.position.column * self.config.branch_spacing))); 252 | circle.setAttribute("cy", (self.config.top_margin + (d.position.row * self.config.circle_spacing))); 253 | circle.setAttribute("r", self.config.circle.radius); 254 | circle.setAttribute("fill", d.position.color); 255 | }) 256 | 257 | line_group.setAttribute("stroke", "#000"); 258 | line_group.setAttribute("stroke-width", this.config.line_width); 259 | line_group.setAttribute("fill", "none"); 260 | 261 | 262 | line_array.forEach(function(d) { 263 | var path = line_group.appendChild(create_element("path")); 264 | path.setAttribute("d", create_line(d)); 265 | path.setAttribute("stroke", d[0].color); 266 | }) 267 | 268 | location.append(svg); 269 | svg.setAttribute("width", svg.childNodes[1].getBoundingClientRect().width + this.config.left_margin); 270 | svg.setAttribute("height", height); 271 | return svg; 272 | }; 273 | 274 | module.exports = GitGraph; 275 | -------------------------------------------------------------------------------- /lib/logparser.js: -------------------------------------------------------------------------------- 1 | var author_regex = /([^<]+)<([^>]+)>/; 2 | var headers = { 3 | 'Author': function(current_commit, author) { 4 | var capture = author_regex.exec(author); 5 | if(capture) { 6 | current_commit.author_name = capture[1].trim(); 7 | current_commit.author_email = capture[2].trim(); 8 | } else { 9 | current_commit.author_name = author; 10 | } 11 | }, 12 | 13 | 'Commit': function(current_commit, author) { 14 | var capture = author_regex.exec(author); 15 | if (capture) { 16 | current_commit.committer_name = capture[1].trim(); 17 | current_commit.committer_email = capture[2].trim(); 18 | } 19 | else { 20 | current_commit.committer_name = author; 21 | } 22 | }, 23 | 24 | 'AuthorDate': function(current_commit, date) { 25 | current_commit.author_date = date; 26 | }, 27 | 28 | 'CommitDate': function(current_commit, date) { 29 | current_commit.commit_date = date; 30 | }, 31 | 32 | 'Reflog': function(current_commit, data) { 33 | current_commit.reflog_name = data.substring(0, data.indexOf(' ')); 34 | var author = data.substring(data.indexOf(' ') + 2, data.length - 1); 35 | var capture = author_regex.exec(author); 36 | if (capture) { 37 | current_commit.reflog_author_name = capture[1].trim(); 38 | current_commit.reflog_author_email = capture[2].trim(); 39 | } 40 | else { 41 | current_commit.reflog_author_name = author; 42 | } 43 | }, 44 | }; 45 | 46 | var parse_git_log = function(data) { 47 | var commits = []; 48 | var current_commit; 49 | var temp_file_change = []; 50 | var parse_commit_line = function(row) { 51 | if (!row.trim()) return; 52 | current_commit = { refs: [], file_line_diffs: [] }; 53 | var ss = row.split('('); 54 | var sha1s = ss[0].split(' ').slice(1).filter(function(sha1) { return sha1 && sha1.length; }); 55 | current_commit.sha1 = sha1s[0]; 56 | current_commit.parents = sha1s.slice(1); 57 | if (ss[1]) { 58 | var refs = ss[1].slice(0, ss[1].length - 1); 59 | current_commit.refs = refs.split(', '); 60 | } 61 | commits.push(current_commit); 62 | parser = parse_header_line; 63 | } 64 | var parse_header_line = function(row) { 65 | if (row.trim() == '') { 66 | parser = parse_commit_message; 67 | } else { 68 | for (var key in headers) { 69 | if (row.indexOf(key + ': ') == 0) { 70 | headers[key](current_commit, row.slice((key + ': ').length).trim()); 71 | return; 72 | } 73 | } 74 | } 75 | } 76 | var parse_commit_message = function(row, index) { 77 | if(/:[\d]+\s[\d]+\s[\d|\w]+.../g.test(rows[index + 1])) { 78 | //if (/[\d-]+\t[\d-]+\t.+/g.test(rows[index + 1])) { 79 | parser = parse_file_changes; 80 | return; 81 | } 82 | if (rows[index + 1] && rows[index + 1].indexOf('commit ') == 0) { 83 | parser = parse_commit_line; 84 | return; 85 | } 86 | if (current_commit.message) 87 | current_commit.message += '\n'; 88 | else current_commit.message = ''; 89 | current_commit.message += row.trim(); 90 | } 91 | var parse_file_changes = function(row, index) { 92 | if (rows.length === index + 1 || rows[index + 1] && rows[index + 1].indexOf('commit ') === 0) { 93 | var total = [0, 0, 'Total']; 94 | for (var n = 0; n < current_commit.file_line_diffs.length; n++) { 95 | var file_line_diff = current_commit.file_line_diffs[n]; 96 | if (!isNaN(parseInt(file_line_diff[0], 10))) { 97 | total[0] += file_line_diff[0] = parseInt(file_line_diff[0], 10); 98 | } 99 | if (!isNaN(parseInt(file_line_diff[1], 10))) { 100 | total[1] += file_line_diff[1] = parseInt(file_line_diff[1], 10); 101 | } 102 | } 103 | current_commit.file_line_diffs.splice(0,0, total); 104 | parser = parse_commit_line; 105 | return; 106 | } 107 | if(row[0] == ':') { 108 | var val = row[row.lastIndexOf('... ') + 4]; 109 | temp_file_change.push(val); 110 | } 111 | else { 112 | current_commit.file_line_diffs.push(row.split('\t').concat(temp_file_change.shift())); 113 | } 114 | } 115 | var parser = parse_commit_line; 116 | var rows = data.split('\n'); 117 | rows.forEach(function(row, index) { 118 | parser(row, index); 119 | }); 120 | 121 | commits.forEach(function(commit) { commit.message = (typeof commit.message) === 'string' ? commit.message.trim() : ''; }); 122 | return commits; 123 | }; 124 | 125 | module.exports = parse_git_log; 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-log", 3 | "main": "./lib/git-log.js", 4 | "version": "0.4.1", 5 | "description": "This package graphs your git commits", 6 | "repository": "https://github.com/nikhilkalige/git-log", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">0.50.0" 10 | }, 11 | "dependencies": { 12 | "atom-space-pen-views": "^2.0.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /resources/git-log.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikhilkalige/git-log/396efa75d2812d4af468521c6072d6bbb83b9e86/resources/git-log.gif -------------------------------------------------------------------------------- /styles/git-log.less: -------------------------------------------------------------------------------- 1 | @import 'syntax-variables'; 2 | 3 | @padding: 1em; 4 | @margin: @padding - 0.05em; 5 | @padding-bigger: 1.5em; 6 | 7 | @gravatar-size: 64px; 8 | @info-padding-top: 0.5em; 9 | 10 | .git-log { 11 | overflow: auto; 12 | background-color: @syntax-gutter-background-color; 13 | color: @syntax-text-color; 14 | overflow: hidden; 15 | display: flex; 16 | flex-direction: column; 17 | 18 | h2 { 19 | margin: 0px; 20 | font-family: inherit; 21 | font-size: 1.1em; 22 | } 23 | 24 | p { 25 | margin: 0px; 26 | line-height: inherit; 27 | padding: 0 @padding; 28 | } 29 | 30 | .panels { 31 | display: -webkit-flex; 32 | -webkit-flex-direction: row; 33 | overflow: auto; 34 | } 35 | 36 | .main { 37 | position: relative; 38 | white-space: nowrap; 39 | 40 | h2 { 41 | border-bottom: rgba(255,255,255,.5)1px solid; 42 | background-color: rgba(0,0,0,.3); 43 | padding: 10px 0; 44 | text-align: center; 45 | } 46 | 47 | p { 48 | position: relative; 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | background: @syntax-background-color; 52 | 53 | &:nth-child(odd) { 54 | //background: rgba(0,0,0,0.3); 55 | } 56 | 57 | &:nth-child(even) { 58 | //background: rgba(0,0,0,0.3); 59 | } 60 | 61 | span { 62 | overflow: hidden; 63 | text-overflow: ellipsis; 64 | display: block; 65 | } 66 | } 67 | 68 | .list, .list-head { 69 | position: relative; 70 | text-align: left; 71 | margin: 0 -@margin; 72 | } 73 | } 74 | 75 | .info { 76 | flex-basis: 35%; 77 | flex-direction: column; 78 | flex-shrink: 0; 79 | position: relative; 80 | //background-color: rgba(0,0,0,0.6); 81 | 82 | h2 { 83 | border: none; 84 | text-align: left; 85 | line-height: inherit; 86 | padding: 0 1em; 87 | flex-shrink: 0; 88 | border: none; 89 | background: none !important; 90 | font-size: 1em; 91 | font-weight: bold; 92 | 93 | &:first-child { 94 | padding-top: @info-padding-top; 95 | } 96 | 97 | span { 98 | font-weight: normal; 99 | font-size: 1em; 100 | padding-left: 8px; 101 | } 102 | } 103 | p { 104 | padding-top: 10px; 105 | padding-top: 10px; 106 | //margin-bottom: 0.5em; 107 | //background: none !important; 108 | flex-shrink: 0; 109 | } 110 | } 111 | 112 | 113 | 114 | .info-image { 115 | position: absolute; 116 | top: @info-padding-top; 117 | right: 1em; 118 | 119 | img { 120 | width: @gravatar-size; 121 | 122 | &:first-child { 123 | margin-right: 1px; 124 | } 125 | } 126 | } 127 | 128 | 129 | .column { 130 | text-align: center; 131 | flex: 1 0; 132 | padding: 0 @padding; 133 | } 134 | 135 | .graph { 136 | position: absolute; 137 | z-index: 1; 138 | //lex: 0 0 auto !important; 139 | //padding: 0 @padding; 140 | } 141 | 142 | svg { 143 | margin: 0 1em; 144 | } 145 | 146 | .table { 147 | width: 100%; 148 | display: flex; 149 | flex: 1 0; 150 | } 151 | 152 | /*.comments { 153 | flex-basis: 70%; 154 | p::before, p::after { 155 | content: ""; 156 | display: block; 157 | position: absolute; 158 | top: 0px; 159 | bottom: 0px; 160 | background: inherit; 161 | } 162 | p::after { 163 | z-index: 4; 164 | opacity: 0; 165 | } 166 | } 167 | 168 | .commit, .date, .author { 169 | flex-basis: 10%; 170 | } 171 | 172 | .commit p { 173 | padding-left: @padding-bigger; 174 | } 175 | */ 176 | .resize-handle { 177 | position: absolute; 178 | width: 10px; 179 | top: 0; 180 | bottom: 0; 181 | right: -5px; 182 | cursor: col-resize; 183 | } 184 | 185 | .added { 186 | background: url(atom://git-log/resources/added.png); 187 | } 188 | 189 | .deleted { 190 | background: url(atom://git-log/resources/deleted.png); 191 | } 192 | 193 | .modified { 194 | background: url(atom://git-log/resources/modified.png); 195 | } 196 | } 197 | 198 | 199 | 200 | .git-log { 201 | table { 202 | table-layout: fixed; 203 | } 204 | 205 | table,tbody,th,td,tr { 206 | border:0px; 207 | padding:1px; 208 | margin:0px; 209 | } 210 | 211 | td { 212 | overflow: hidden; 213 | text-overflow: ellipsis; 214 | } 215 | 216 | table, thead { 217 | width: 100%; 218 | } 219 | 220 | thead p { 221 | font-size: 1.1em; 222 | font-weight: normal; 223 | padding-top: 8px; 224 | padding-bottom: 8px; 225 | text-align: center; 226 | } 227 | 228 | .main { 229 | tbody tr td:first-child { 230 | & > p:after { 231 | content: ""; 232 | display: block; 233 | position: absolute; 234 | top: 0px; 235 | bottom: 0px; 236 | right: 0; 237 | left: 0; 238 | z-index: 3; 239 | opacity: 0; 240 | } 241 | } 242 | } 243 | 244 | .comments { 245 | flex-basis: 70%; 246 | } 247 | 248 | .commit, .date, .author { 249 | flex-basis: 10%; 250 | } 251 | 252 | .info { 253 | margin-top: 2px; 254 | margin-bottom: 2px; 255 | } 256 | 257 | .info-data { 258 | //border-top: solid 5px rgba(0,0,0,0.3); 259 | margin: 2px; 260 | margin-bottom: 1px; 261 | padding-bottom: 8px; 262 | position: relative; 263 | //background-color: rgba(0,0,0,.3); 264 | background: @syntax-background-color; 265 | flex-shrink: 0; 266 | 267 | p { 268 | background: none; 269 | padding-bottom: 1.3em; 270 | } 271 | } 272 | 273 | .info-file { 274 | display: flex; 275 | flex-shrink: 0; 276 | //border-top: solid 1px; 277 | margin-top: 1px; 278 | margin-bottom: 2px; 279 | 280 | .list-head { 281 | border-bottom: solid 1px; 282 | margin: 0 -@margin; 283 | } 284 | .column { 285 | text-align: left; 286 | } 287 | p { 288 | //background-color: rgba(0,0,0,0.3); 289 | background: @syntax-background-color; 290 | } 291 | 292 | tbody tr { 293 | &:last-child p { 294 | padding-bottom: 4px; 295 | } 296 | td:nth-child(4), td:nth-child(5) { 297 | text-align: center; 298 | } 299 | } 300 | } 301 | .log-highlight p { 302 | background-color:@syntax-selection-color; 303 | } 304 | } 305 | --------------------------------------------------------------------------------