diff --git a/native/v8_extensions/git.js b/native/v8_extensions/git.js index b240204ff..60de65ed1 100644 --- a/native/v8_extensions/git.js +++ b/native/v8_extensions/git.js @@ -7,6 +7,7 @@ var $git = {}; native function getStatus(path); native function isIgnored(path); native function checkoutHead(path); + native function getDiffStats(path); function GitRepository(path) { var repo = getRepository(path); @@ -20,5 +21,6 @@ var $git = {}; GitRepository.prototype.getStatus = getStatus; GitRepository.prototype.isIgnored = isIgnored; GitRepository.prototype.checkoutHead = checkoutHead; + GitRepository.prototype.getDiffStats = getDiffStats; this.GitRepository = GitRepository; })(); diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm index 5ac5f23b7..9997feddd 100644 --- a/native/v8_extensions/git.mm +++ b/native/v8_extensions/git.mm @@ -105,6 +105,80 @@ public: } } + CefRefPtr GetDiffStats(const char *path) { + if (!exists) { + return CefV8Value::CreateNull(); + } + + char *copiedPath = (char *)malloc(sizeof(char) * (strlen(path) + 1)); + strcpy(copiedPath, path); + git_diff_options options; + memset(&options, 0, sizeof(options)); + git_strarray paths; + paths.count = 1; + paths.strings = &copiedPath; + options.pathspec = paths; + options.context_lines = 1; + options.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH; + + git_reference *head; + if (git_repository_head(&head, repo) != GIT_OK) { + return CefV8Value::CreateNull(); + } + + const git_oid* sha = git_reference_oid(head); + git_commit *commit; + int commitStatus = git_commit_lookup(&commit, repo, sha); + git_reference_free(head); + if (commitStatus != GIT_OK) { + return CefV8Value::CreateNull(); + } + + git_tree *tree; + int treeStatus = git_commit_tree(&tree, commit); + git_commit_free(commit); + if (treeStatus != GIT_OK) { + return CefV8Value::CreateNull(); + } + + git_diff_list *diffs; + int diffStatus = git_diff_workdir_to_tree(repo, &options, tree, &diffs); + if (diffStatus != GIT_OK) { + return CefV8Value::CreateNull(); + } + + git_diff_patch *patch; + int patchStatus = git_diff_get_patch(&patch, NULL, diffs, 0); + git_diff_list_free(diffs); + if (patchStatus != GIT_OK) { + return CefV8Value::CreateNull(); + } + + int added = 0; + int deleted = 0; + int hunks = git_diff_patch_num_hunks(patch); + for (int i = 0; i < hunks; i++) { + int lines = git_diff_patch_num_lines_in_hunk(patch, i); + for (int j = 0; j < lines; j++) { + char lineType[2]; + lineType[1] = '\0'; + if (git_diff_patch_get_line_in_hunk(lineType, NULL, NULL, NULL, NULL, patch, i, j) == GIT_OK) { + if (strcmp(lineType, "+") == 0) { + added++; + } else if(strcmp(lineType, "-") == 0) { + deleted++; + } + } + } + } + git_diff_patch_free(patch); + + CefRefPtr result = CefV8Value::CreateObject(NULL); + result->SetValue("added", CefV8Value::CreateInt(added), V8_PROPERTY_ATTRIBUTE_NONE); + result->SetValue("deleted", CefV8Value::CreateInt(deleted), V8_PROPERTY_ATTRIBUTE_NONE); + return result; + } + IMPLEMENT_REFCOUNTING(GitRepository); }; @@ -156,6 +230,12 @@ bool Git::Execute(const CefString& name, return true; } + if (name == "getDiffStats") { + GitRepository *userData = (GitRepository *)object->GetUserData().get(); + retval = userData->GetDiffStats(arguments[0]->GetStringValue().ToString().c_str()); + return true; + } + return false; } diff --git a/src/app/git.coffee b/src/app/git.coffee index ea4127744..c8369a53c 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -63,3 +63,7 @@ class Git checkoutHead: (path) -> @repo.checkoutHead(@relativize(path)) + + getDiffStats: (path) -> + stats = @repo.getDiffStats(@relativize(path)) + stats or {'added': 0, 'deleted': 0} diff --git a/src/packages/status-bar/spec/status-bar-spec.coffee b/src/packages/status-bar/spec/status-bar-spec.coffee index 3f9f47d11..e0ad16b1e 100644 --- a/src/packages/status-bar/spec/status-bar-spec.coffee +++ b/src/packages/status-bar/spec/status-bar-spec.coffee @@ -166,3 +166,12 @@ describe "StatusBar", -> fs.write(path, originalPathText) $(window).trigger 'focus' expect(statusBar.gitStatusIcon).not.toHaveClass('modified-status-icon') + + it "displays the diff stat for modified files", -> + fs.write(path, "i've changed for the worse") + rootView.open(path) + expect(statusBar.gitStatusIcon).toHaveText('+1,-1') + + it "displays the diff stat for new files", -> + rootView.open(newPath) + expect(statusBar.gitStatusIcon).toHaveText('+1') diff --git a/src/packages/status-bar/src/status-bar.coffee b/src/packages/status-bar/src/status-bar.coffee index 83a3e4833..fc9080cc7 100644 --- a/src/packages/status-bar/src/status-bar.coffee +++ b/src/packages/status-bar/src/status-bar.coffee @@ -79,8 +79,18 @@ class StatusBar extends View @gitStatusIcon.removeClass().addClass('git-status octicons') if @buffer.getGit()?.isPathModified(path) @gitStatusIcon.addClass('modified-status-icon') - else if @buffer.getGit()?.isPathNew(path) + stats = @buffer.getGit().getDiffStats(path) + if stats.added and stats.deleted + @gitStatusIcon.text("+#{stats.added},-#{stats.deleted}") + else if stats.added + @gitStatusIcon.text("+#{stats.added}") + else if stats.deleted + @gitStatusIcon.text("-#{stats.deleted}") + else + @gitStatusIcon.text('') + else if @buffer.getGit()?.isPathNew(path) @gitStatusIcon.addClass('new-status-icon') + @gitStatusIcon.text("+#{@buffer.getLineCount()}") updatePathText: -> if path = @editor.getPath() diff --git a/static/status-bar.css b/static/status-bar.css index b3e6c1069..09ea8bb29 100644 --- a/static/status-bar.css +++ b/static/status-bar.css @@ -17,7 +17,6 @@ } .status-bar .branch-label { - padding-left: 5px; vertical-align: baseline; } @@ -27,7 +26,7 @@ margin-top:-2px; } -.status-bar .octicons { +.status-bar .octicons:before { font-family: 'Octicons Regular'; font-size: 14px; width: 14px; @@ -36,6 +35,7 @@ -webkit-font-smoothing: antialiased; display: inline-block; vertical-align: middle; + margin-right: 5px; } .status-bar .branch-icon:before { @@ -59,4 +59,3 @@ .status-bar .new-status-icon:before { content: "\f26b"; } -