├── .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 | 
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 |
--------------------------------------------------------------------------------